Skip to content
/api/v1/payment/create-checkout-session

Create a Stripe-hosted Checkout session for a subscription upgrade.

Create a Stripe-hosted Checkout session for a subscription upgrade. EXEMPT (`cost: 0`). Returns the Stripe URL — frontend MUST redirect via `window.location.href`.

Why use this

Server-side wrapper for `stripe.checkout.Session.create` (mode=subscription) — returns a Stripe-hosted checkout URL where the user enters card details and confirms the upgrade. EXEMPT (`cost: 0`). The frontend MUST redirect (not iframe-embed). On successful payment, Stripe webhooks `checkout.session.completed` → `_handle_checkout_completed` activates the subscription on `user_plans` (sets `plan`, `stripe_subscription_id`, `current_period_end`). On cancellation / failure the user is redirected back to `/account/subscription?cancelled=true`. Free plan (no `stripe_price_id`) cannot be checked out — returns 400 `BAD_REQUEST` if `plan.stripe_price_id` is null.

Common use case

Pricing-page 'Upgrade to Paid' button: get plan id from /api/v1/plans/ → POST here with `?plan_id=2` → redirect browser to the returned URL → Stripe-hosted checkout → on success Stripe redirects back to `/account/subscription?session_id=...` → SPA polls /api/v1/billing/subscription for the activated state.

Stripe-hosted subscription Checkout session for plan upgrades. EXEMPT (cost: 0). Wraps stripe.checkout.Session.create(mode='subscription'). The frontend MUST redirect (not iframe-embed) — Stripe blocks framing. On successful payment Stripe webhooks checkout.session.completed to /api/v1/payments/webhook → _handle_checkout_completed activates the subscription on user_plans (sets plan, stripe_subscription_id, current_period_end). The Free plan cannot be checked out (no stripe_price_id) — gate the 'Upgrade' button server-side rather than rendering for Free. To list available plans use GET /api/v1/plans/; to check post-upgrade state use GET /api/v1/billing/subscription; for self-serve subscription management (cancel, change card) once on a paid plan use POST /api/v1/account/billing-portal. For one-time USD wallet top-ups (NOT subscription upgrades) use POST /api/v1/payments/create-intent.

Parameters

NameInRequiredDefaultAllowedDescriptionExample
plan_idqueryrequiredPlan database ID (NOT name) — the integer `id` from [GET /api/v1/plans/](/docs/account/billing-and-subscription/get-plans). Returns 400 `BAD_REQUEST` if missing or non-numeric, 404 `NOT_FOUND` if the plan does not exist or is inactive, 400 `BAD_REQUEST` if the plan has no `stripe_price_id` (Free plan).2

Response schema

FieldTypeNullableDescription
statusstringnoApiResponse envelope status — `success` on 200, `error` on 4xx/5xx.
request_idstringyesPer-request correlation ID.
timestampstringnoISO-8601 UTC timestamp.
dataobjectnoCheckout-session payload.
data.urlstringnoStripe Checkout-hosted page URL in `https://checkout.stripe.com/c/pay/cs_XXXXXXX#YYYYY` format. REDIRECT the browser via `window.location.href = response.data.url` — do NOT iframe-embed (Stripe blocks framing of checkout pages). On success, Stripe redirects back to `${FRONTEND_URL}/account/subscription?session_id={CHECKOUT_SESSION_ID}`; on cancel, to `${FRONTEND_URL}/account/subscription?cancelled=true`. The Checkout session embeds metadata (`user_uuid`, `plan_id`, `plan_name`) so the `checkout.session.completed` webhook handler can activate the right subscription on the right user.

Sample response

·
  • "status": "success"
  • "request_id": "req_3OqK2jK9L8pQ4xZ6"
  • "timestamp": "2026-05-02T15:51:00.000Z"
  • "data":
    • "url": "https://checkout.stripe.com/c/pay/cs_test_b1Yq8nL7aP2kT6sB1mDfHj9vC3xRtKzE5wQ8jN4uM2pX7yI6oA9hT0gU1vF#fidkdWxOYHwnPyd1blpxYHZxWjA0SF9MUFc2dVZxYG1QSXNqcGhJQDR9aGhNNXxPSk09Q1xLTHN1MnxwfFBIYDU8YzVtXX1HSUtAaD1KaHM3RkB"
    }
}

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/payment/create-checkout-session" \
  -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).