Skip to main content

Pricing Tier

A pricing tier is a purchasable plan attached to an agent. One agent has many tiers.

Schema (agent_pricing_tiers)

ColumnTypeNotes
idUUIDPK
agentIdUUIDFK → agents
tierNametext"Free", "Growth", "Pro", "Enterprise"…
isFreeboolIf true, no Stripe interaction at checkout
pricenumericDecimal string (e.g. "49.00"); zero for free
currencytextISO 4217 (usd, inr, …)
billingIntervalenummonth | year | one_time
featuresIncludedtext[]Bullet list shown on the pricing card
recommendedboolVisually highlights the tier
trialDaysintTrial length; 0 = no trial
sortOrderintRender order
stripeProductIdtext, nullableSet only when STRIPE_SECRET_KEY is configured and isFree: false
stripePriceIdtext, nullableSame

Endpoints

VerbPathGuard
GET/agents/:agentId/pricingpublic
POST/agents/:agentId/pricingplatform_admin
DELETE/agents/:agentId/pricing/:tierIdplatform_admin

Stripe coupling

When you POST /agents/:agentId/pricing with isFree: false:

  1. Backend calls Stripe products.create({ name }).
  2. Backend calls Stripe prices.create({ product, unit_amount, currency, recurring }).
  3. Resulting stripeProductId + stripePriceId are stored on the tier row.
  4. Subsequent checkout flows use these IDs.

When you DELETE:

  1. Backend archives the Stripe product (products.update({ active: false })).
  2. Backend soft-deletes the tier row.
  3. Existing subscriptions on the tier are unaffected — Stripe continues billing until they cancel.

Checkout

POST /agents/:agentId/pricing/:tierId/checkout with body { returnUrl }.

  • Free tier → creates user_agent_subscriptions row synchronously, returns { status: 'active', subscriptionId, returnUrl }.
  • Paid tier → returns { status: 'redirect', url } pointing to Stripe Checkout. After payment, Stripe redirects to returnUrl?session_id=... and the webhook upserts the subscription.

returnUrl is validated against the agent's redirectUris[] and productDomain. Localhost is allowed only in dev (NODE_ENV !== 'production').

DTO

type PricingTierDto = {
id: string;
agentId: string;
tierName: string;
isFree: boolean;
price: string; // decimal string for precision
currency: string;
billingInterval: "month" | "year" | "one_time";
featuresIncluded: string[];
recommended: boolean;
trialDays: number;
sortOrder: number;
stripeProductId: string | null;
stripePriceId: string | null;
createdAt: string;
updatedAt: string;
};

Frontend rendering

useAgentPricing(agentId) from src/api/ returns the array. toDisplayAgent() collapses it into one of three labels:

ConditionDisplay label
All tiers are freeFree
At least one free and at least one paidFreemium
All paidPaid

Plus a price line: From $X / mo based on the lowest paid tier.