Skip to content

Open Stripe Billing Portal for the active team

GET
/api/billing/portal/
curl --request GET \
--url 'http://localhost:8000/api/billing/portal/?lang=ru&return_url=https%3A%2F%2Fai.aikynetix.app%2Fsettings%3Fsection%3Dsubscription' \
--header 'Authorization: Bearer <token>'

Mints a one-shot Stripe Billing Portal session for the team’s existing Stripe customer and returns the hosted URL. The Portal is the single surface for every paid-tier lifecycle action (ADR-0019 §8):

  • Cancel subscription (defaults to cancel-at-period-end)
  • Reactivate a scheduled cancel
  • Switch plans (Starter ↔ Pro ↔ Org) with Stripe’s automatic proration
  • Update payment method
  • View invoice history + download PDF receipts

Owner-only. Members get 403. The endpoint resolves the team’s most recent paid Subscription with a non-null stripe_customer_id; Free / Strategic teams return 404 (they have no Stripe customer). The FE shows the tier-specific SubscriptionPanel for the 404 case — see the Storybook page-level stories.

Stripe enforces cancel-at-period-end (configured via the runbook), so the buyer keeps access through the period they paid for. When that period elapses Stripe sends customer.subscription.deleted and our webhook auto-falls-back to Free with 5 sessions.

lang
string

FE language code (en / es / ru — mirrors apps/web SUPPORTED_LANGS). Mapped to Stripe locale so the Portal page renders in the language the buyer picked in Settings → Language. Unknown / unset → auto (Stripe’s documented fallback). Append here when the SPA picker grows.

Examples
ExampleRussian
ru
return_url
string

Where the Portal sends the user when they close it. Validated against settings.app_base_url — third-party URLs are rejected silently (open-redirect guard) and the default /settings?section=subscription is substituted. Pass an absolute URL on the FE.

Examples
ExampleSame-originDefault

Same-origin default

https://ai.aikynetix.app/settings?section=subscription
Media typeapplication/json
object
url
required

Hosted Stripe Billing Portal URL. Redirect the buyer here in a new tab so they can manage their subscription. Single-use; mint a fresh one per click.

string
Examples
ExamplePortalSessionMinted

Portal session minted

{
"url": "https://billing.stripe.com/p/session/test_YWNjdF8xUVhYWVoxMjM_a1b2c3d4e5"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string
Examplegenerated
{
"detail": "example"
}

Caller is not the team owner. Member / non-owner admins see this — the Portal manages billing, which is owner-scoped. Also returned to a partner aik_… API key: billing writes require the Firebase scheme (billing is read-only for keys).

Team has no Stripe customer record. Free + Strategic teams hit this (they have no paid Subscription with a stripe_customer_id). The FE should render the tier-specific SubscriptionPanel instead of redirecting.