Skip to content
/api/v1/account/billing-portal

Returns a Stripe Customer Portal session URL.

Returns a Stripe Customer Portal session URL. The frontend MUST redirect the user to this URL via `window.location.href = response.url` (Stripe disallows iframe embedding via X-Frame-Options: DENY). Free users without a Stripe customer get a 400 with `error: no_stripe_customer`.

Why use this

Single endpoint for self-serve subscription management — update card, view invoices, cancel subscription, change billing email. EXEMPT (`cost: 0`). Returns a short-lived Stripe-hosted Customer Portal session URL. The frontend MUST redirect (NOT iframe-embed) — Stripe disallows embedding via `X-Frame-Options: DENY` on the portal HTML response. Sessions expire ~1 hour after creation; if the user idle-bounces back, generate a new one. Customers without a Stripe customer record (free-tier users who never upgraded) get a 400 with `error: no_stripe_customer` — gate the 'Manage Billing' button by `plan === 'paid'` to avoid the dead-click.

Common use case

Customer clicks 'Manage Billing' on the dashboard → frontend POSTs here → redirects to Stripe-hosted portal → returns to the app on close (Stripe's `return_url` brings them back to `/account`).

Stripe Customer Portal session URL — single endpoint for self-serve subscription management (update card, view invoices, cancel subscription, change billing email). EXEMPT (cost: 0). The frontend redirect pattern is a HARD requirement (not a recommendation): window.location.href = response.url. Iframe embedding is blocked by Stripe's X-Frame-Options: DENY on the portal HTML and will silently 0x0 in the browser. Free-tier users without stripe_customer_id get a 400 with error: no_stripe_customer — gate the 'Manage Billing' button by checking GET /api/v1/account/balance plan === 'paid' first. For viewing the per-charge wallet ledger inline use GET /api/v1/billing/history; for invoice PDF downloads use GET /api/v1/billing/invoices (which fetches the same Stripe data via a server-side API call, no redirect needed).

Response schema

FieldTypeNullableDescription
urlstringnoStripe Customer Portal session URL. Short-lived (~1 hour TTL). REDIRECT the browser to this URL via `window.location.href = response.url` — DO NOT iframe-embed (Stripe sets `X-Frame-Options: DENY` on the portal response). After the user finishes (or closes the portal), Stripe redirects back to `${FRONTEND_URL}/account` per the server-configured `return_url`.

Sample response

·
  • "url": "https://billing.stripe.com/p/session/test_YWNjdF8xUXBlV0VLRzQzS3JCbnJ1eA0HDJI"
}

Errors

StatusLabelDescription
200OKRequest succeeded.
400Bad RequestInvalid query, body, or path parameter.
401UnauthorizedMissing or invalid Authorization header / api_Token.
402Payment RequiredInsufficient token balance for this call. Top up
429Too Many RequestsRate limit exceeded for your tier (see /pricing for tier limits). Tier limits
500Server ErrorUnexpected server-side failure. Retry with backoff; report if persistent.

Code samples

curl -X POST "https://api.finradar.ai/api/v1/account/billing-portal" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Generate an API key in /account/credentials to run live queries (literal YOUR_API_KEY placeholder shown until then).