API reference, Console billing
Start a plan subscription (Stripe Checkout, subscription mode)
POST/v1/console/subscription/checkoutoperation id createSubscriptionCheckout
Creates a Stripe Checkout Session in subscription mode for the plan's monthly price (excluding VAT; Stripe
Tax adds VAT or applies the reverse charge). The subscription starts when Stripe reports the first payment;
the weekly allowance then runs in 7-day weeks from that moment. An account with a live subscription gets 409
subscription_exists: change plans with updateSubscription instead.
Founding round: every plan has a limited number of seats (seats_left in listPlans). When none is left the
answer is 409 plan_sold_out with hints: join_waiting_list (joinWaitingList), choose_plan (a plan with
seats left, when there is one) and pay_as_you_go. An open checkout holds its seat until it expires (an hour);
an account invited from the waiting list has a seat held for it for 72 hours.
Authentication. Console session cookie (__Host-dex_session), or an app session token (Authorization: Bearer dex_ses_...). With the cookie, state-changing routes also need the x-dex-csrf header.
Parameters
x-dex-csrfheader · stringcsrf_token returned by createSession (or handed over in the completeGoogleSignIn redirect). Required
with the session cookie on every state-changing console route; not used with a bearer app session.32 to 64 characters
idempotency-keyheader · stringidempotent-replayed: true and the original response: the same id, created, model,
answers and usage, including the original charge fields. The same key with a different body answers 409.1 to 255 characterspattern ^[\x21-\x7E]+$
Request body
application/json, schema SubscriptionCheckoutRequest
planPlanIdrequiredone of "base", "hot", "fierce"
return_pathstringpattern ^/[A-Za-z0-9/_-]{0,200}$
Responses
201Checkout session created; send the browser to url.
Headers: x-request-id
idstringrequiredobjectstringrequiredalways "checkout_session"
kindstringrequiredone of "top_up", "subscription", "reset"
planstring | nullrequiredone of "base", "hot", "fierce", null
urlstringrequiredformat uri
amount_centsintegerrequiredPlan.prices, PlanList.top_up_packs) in the currency
Stripe presents; the balance and the plan are the same in every currency.currencystringrequiredamount_cents (always EUR); see charge_currency for the payment.always "EUR"
charge_currencystring | nullrequiredbilling_currency, or EUR for
a top-up of a free amount. null when Stripe picks it from the buyer's location at checkout. New
currencies may appear within v1.one of "EUR", "USD", "GBP", "INR", null
expires_atstringrequiredformat date-time
400The request could not be read. Codes: invalid_json (not JSON, not UTF-8, or a \u escape that is half of a
surrogate pair, such as "\ud800" without its low half), duplicate_key (a JSON object
repeats a key; param is the JSON pointer of the repeated member, such as /questions/q1/options/a),
unsupported_media_type (not application/json) and invalid_header (a malformed idempotency-key or
x-client-request-id; param names the header).
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
401Missing, unknown, revoked or expired credentials.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
403The credentials are valid but not allowed to do this (missing scope, suspended account or failed CSRF check).
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
409The idempotency key was used with a different body, or the first request with this key is still running.
Console billing routes also answer 409 for a state conflict: subscription_exists (createSubscriptionCheckout
while a subscription is live, joinWaitingList for a plan the account holds), no_subscription
(updateSubscription, cancelSubscription, createResetCheckout or createBillingPortalSession without one),
plan_sold_out (createSubscriptionCheckout or updateSubscription to a plan with no founding-round seat left;
with hints), key_revoked (updateKey on a revoked key).
Headers: x-request-id, x-client-request-id, retry-after
Error envelope. See Errors for every type and code.
422The request is well-formed JSON but breaks a validation rule. param names the field as a dotted path.
Codes: unknown_field, missing_field, invalid_type (wrong JSON type), invalid_value (right type, value
out of range: an empty or over-long string, a state, instructions, criteria string or option description
of only whitespace, an empty questions or options object, an empty state object or array, a pattern or
allowed-value mismatch such as a label or level with outer whitespace, a state nested deeper than 32 levels, a
bad date or date range), field_not_allowed (options or levels on the wrong question type),
invalid_question_id, too_many_questions, too_many_options, invalid_levels (including two levels equal
after Unicode NFC normalisation and lower-casing), invalid_min_confidence, duplicate_label (two option
labels equal after Unicode NFC normalisation and lower-casing, such as Billing and billing),
state_path_not_found, state_not_json and, on the console, top_up_limit_exceeded.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
429A rate limit or quota is exhausted. Retry after the given number of seconds. For test_daily_quota that is
the time until 00:00 UTC; clients should not retry it automatically.
Headers: x-request-id, x-client-request-id, retry-after, ratelimit-policy, ratelimit
Error envelope. See Errors for every type and code.
500An unexpected error. Any charge was refunded. Retry with the same idempotency key.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
503maintenance: the console's database, sign-in or payments are unavailable for a moment (for
createGoogleSession also: Google sign-in is not configured, or Google's signing keys cannot be fetched).
Retry after retry-after. Nothing was changed.
Headers: x-request-id, retry-after
Error envelope. See Errors for every type and code.
Examples
Illustrative values. Examples show the shape of requests and responses; the numbers in them are not measured results.
Request: base
{
"plan": "base",
"return_path": "/console/billing"
}Response: session
{
"id": "cs_live_b2C3d4E5f6G7h8I9",
"object": "checkout_session",
"kind": "subscription",
"plan": "base",
"url": "https://checkout.stripe.com/c/pay/cs_live_b2C3d4E5f6G7h8I9",
"amount_cents": 999,
"currency": "EUR",
"charge_currency": null,
"expires_at": "2026-10-20T12:30:00Z"
}