Open Stripe Billing Portal for the active team
const url = 'http://localhost:8000/api/billing/portal/?lang=ru&return_url=https%3A%2F%2Fai.aikynetix.app%2Fsettings%3Fsection%3Dsubscription';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”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
ruWhere 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
Same-origin default
https://ai.aikynetix.app/settings?section=subscriptionResponses
Section titled “Responses”object
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.
Examples
Portal session minted
{ "url": "https://billing.stripe.com/p/session/test_YWNjdF8xUVhYWVoxMjM_a1b2c3d4e5"}object
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).
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.