Skip to main content

Subscription checkout

Single endpoint, two paths.

Endpoint

POST /agents/:agentId/pricing/:tierId/checkout

Body: { "returnUrl": "https://app.my-agent.com/post-checkout" }

Free tier path

Return URL validation

The backend rejects any returnUrl that doesn't match one of:

  1. An entry in the agent's redirectUris[].
  2. The agent's productDomain (origin match).
  3. (Dev only) any http://localhost:* origin.

Mismatch → 422 UNPROCESSABLE_ENTITY with error.code === 'INVALID_RETURN_URL'. This is the marketplace's primary defense against open redirects via the checkout flow.

Race: did the webhook arrive yet?

After Stripe redirects the user back to returnUrl, there is a small window where the webhook hasn't been processed. Recommended UX:

  • On the post-checkout page, call GET /me/subscriptions/:agentId once.
  • If 404 / not active, show "Finalizing your subscription…" and poll every 1.5s up to 15s.
  • Then refresh.

The webhook usually lands in < 1s, but TLS + signature verification + DB write can occasionally exceed 3s.

Existing subscription handling

If the user already has an active or trialing subscription on the same agent:

  • Free → paid: backend creates a Stripe Checkout that includes a proration_behavior: 'create_prorations' if you wire it that way (current default: just create a new sub; the webhook update merges it via partial unique index).
  • Paid → paid (upgrade): goes through Stripe's checkout but Stripe handles the proration on its side.
  • Paid → free: not currently supported via checkout; user must cancel first.

Errors

StatusCodeCause
401UNAUTHORIZEDNot signed in
404NOT_FOUNDAgent or tier doesn't exist
422INVALID_RETURN_URLreturnUrl not in allow-list
503SERVICE_UNAVAILABLEStripe down or STRIPE_SECRET_KEY missing