Pricing Tier
A pricing tier is a purchasable plan attached to an agent. One agent has many tiers.
Schema (agent_pricing_tiers)
| Column | Type | Notes |
|---|---|---|
id | UUID | PK |
agentId | UUID | FK → agents |
tierName | text | "Free", "Growth", "Pro", "Enterprise"… |
isFree | bool | If true, no Stripe interaction at checkout |
price | numeric | Decimal string (e.g. "49.00"); zero for free |
currency | text | ISO 4217 (usd, inr, …) |
billingInterval | enum | month | year | one_time |
featuresIncluded | text[] | Bullet list shown on the pricing card |
recommended | bool | Visually highlights the tier |
trialDays | int | Trial length; 0 = no trial |
sortOrder | int | Render order |
stripeProductId | text, nullable | Set only when STRIPE_SECRET_KEY is configured and isFree: false |
stripePriceId | text, nullable | Same |
Endpoints
| Verb | Path | Guard |
|---|---|---|
| GET | /agents/:agentId/pricing | public |
| POST | /agents/:agentId/pricing | platform_admin |
| DELETE | /agents/:agentId/pricing/:tierId | platform_admin |
Stripe coupling
When you POST /agents/:agentId/pricing with isFree: false:
- Backend calls Stripe
products.create({ name }). - Backend calls Stripe
prices.create({ product, unit_amount, currency, recurring }). - Resulting
stripeProductId+stripePriceIdare stored on the tier row. - Subsequent checkout flows use these IDs.
When you DELETE:
- Backend archives the Stripe product (
products.update({ active: false })). - Backend soft-deletes the tier row.
- 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_subscriptionsrow synchronously, returns{ status: 'active', subscriptionId, returnUrl }. - Paid tier → returns
{ status: 'redirect', url }pointing to Stripe Checkout. After payment, Stripe redirects toreturnUrl?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:
| Condition | Display label |
|---|---|
| All tiers are free | Free |
| At least one free and at least one paid | Freemium |
| All paid | Paid |
Plus a price line: From $X / mo based on the lowest paid tier.