Skip to main content

Subscription

A subscription is the (user, agent, tier) triple that grants a user access to the agent's tier-gated features and meters their usage.

Schema (user_agent_subscriptions)

ColumnTypeNotes
idUUIDPK
userIdUUIDFK → user
agentIdUUIDFK → agents
tierIdUUIDFK → agent_pricing_tiers
statusenumSee below
stripeCustomerIdtextCreated on first paid checkout
stripeSubscriptionIdtext, unique nullableNULL for free tiers
currentPeriodEndtimestamptzWhen the current billing period ends
cancelAtPeriodEndboolIf true, will cancel at period end
canceledAttimestamptz, nullableSet on customer.subscription.deleted
createdAt, updatedAttimestamptz

Partial unique index: (userId, agentId) WHERE status IN ('active', 'trialing'). This guarantees a user has at most one currently-billable row per agent.

Status values

StatusSourceMeaning
activeStripe customer.subscription.created/updated OR free-tier checkoutCurrently paying or on a free tier
trialingStripe (during free trial window)Inside trialDays
past_dueStripe invoice.payment_failedPayment failed; grace period
incompleteStripe customer.subscription.created (3DS pending)Initial charge not yet succeeded
incomplete_expiredStripeInitial charge failed terminally
unpaidStripe (after retries exhausted)All retries failed
canceledStripe customer.subscription.deleted OR explicit user cancelTerminated

Agents should treat active and trialing as "user can use the agent." Everything else is "no access."

Lifecycle

Endpoints

VerbPathGuard
POST/agents/:agentId/pricing/:tierId/checkoutbearer
GET/me/subscriptionsbearer
GET/me/subscriptions/:agentIdbearer
POST/me/subscriptions/:agentId/cancelbearer

DTO

type SubscriptionDto = {
id: string;
agentId: string;
tierId: string;
status: SubscriptionStatus;
stripeCustomerId: string | null;
stripeSubscriptionId: string | null;
currentPeriodEnd: string;
cancelAtPeriodEnd: boolean;
canceledAt: string | null;
tier: PricingTierDto; // embedded
createdAt: string;
updatedAt: string;
};

Cancel

POST /me/subscriptions/:agentId/cancel

  • Free tier — immediate. statuscanceled.
  • Paid tier — cancelAtPeriodEnd: true. The user keeps access until currentPeriodEnd; Stripe sends customer.subscription.deleted then.

Checkout outcomes

Tier typeWhat happensWhat the API returns
FreeRow inserted directly, status active{ status: 'active', subscriptionId, returnUrl }
PaidStripe Checkout Session created{ status: 'redirect', url } — browser follows it

After payment, Stripe calls POST /webhooks/stripe with checkout.session.completed, which upserts the row.

Source of truth for tier

Always. Even if the agent has a marketplace:agentTiers hint in the ID token, the live subscription is authoritative. Call GET /me/subscriptions/:agentId whenever you need to read tier and you haven't read it in the last few seconds.

See also