Skip to main content

Billing API

The Billing API exposes the JWT-authenticated endpoints the dashboard uses to upgrade, downgrade, view invoices, and open the Stripe Customer Portal.

Base URL: https://api.zeridion.com/platform/v1

Every endpoint below requires a valid JWT obtained from POST /platform/v1/auth/login.

Stripe-only. Zeridion uses Stripe for subscription management, invoicing, and Stripe Tax. There is no second billing backend — every monetary action below ultimately resolves to a Stripe API call.

Billing state model

The server tracks subscription health on every project as a BillingState enum:

ValueMeaning
activeSubscription is in good standing — quota and rate limits enforced as normal.
past_dueMost recent invoice failed; Stripe is still retrying. Existing in-flight jobs continue to be processed by workers, but new job creates are blocked unconditionally and return 402 billing_state_blocked until the invoice is paid (any billing state other than active is gated, regardless of plan tier).
unpaidStripe gave up retrying; subscription is suspended. New job creates return 402 billing_state_blocked.
cancelledSubscription was cancelled (by the user or by Stripe). Plan reverts to free on the next state-update webhook.

Trialing collapses to active. Stripe trialing subscriptions are deliberately mapped to active server-side — there is no separate trialing billing state. A trialing tenant gets the same quota and 402-gate treatment as a paying tenant. To differentiate "still on trial" in the UI, call the Stripe Subscriptions API directly with the project's stripe_subscription_id (returned by GET /platform/v1/billing/status) and read the trial_end field on the subscription object. GET /platform/v1/billing/status does not itself return a trial-end timestamp, and the Stripe Customer Portal is a hosted UI that does not expose one programmatically.

State transitions

active is the only state that admits new job creates. Every other state returns 402 billing_state_blocked on POST /flare/v1/jobs and any other create-shaped endpoint. Existing in-flight jobs continue processing in all states except cancelled.


POST /platform/v1/billing/checkout

Create a Stripe Checkout Session for a plan upgrade and return its hosted-page URL. The user's browser is expected to redirect to the returned checkout_url to complete payment.

Authentication: JWT required (Authorization: Bearer <jwt> or the zf_jwt cookie).

The server derives a per-call idempotency key from {userId}:checkout:{epochMinute} so two rapid clicks within the same minute collapse to a single Stripe session rather than two duplicate ones.

Request

POST /platform/v1/billing/checkout
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body

FieldTypeRequiredDescription
plan_idstringYesTarget plan slug. One of starter, pro, or business.
promo_codestringNoStripe promotion code to apply (case-insensitive). Forwarded verbatim to Stripe; both percentage and fixed-amount coupons are supported.
{
"plan_id": "starter",
"promo_code": "LAUNCH50"
}

Response

200 OK

{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_a1b2...",
"session_id": "cs_test_a1b2..."
}
FieldTypeDescription
checkout_urlstringHosted Stripe Checkout URL. Redirect the user here.
session_idstringStripe Checkout Session ID. Useful for diagnostics.

Errors

StatusCodeWhen
400invalid_planplan_id is missing or not one of starter, pro, business.
400invalid_promo_codepromo_code is not active in Stripe or has expired.
401unauthorizedMissing or invalid JWT.

POST /platform/v1/billing/portal

Create a Stripe Customer Portal session so the user can update their payment method, cancel, or download invoices and tax receipts directly from Stripe's hosted UI.

Authentication: JWT required.

Request

POST /platform/v1/billing/portal
Authorization: Bearer <jwt_token>

No request body.

Response

200 OK

{
"portal_url": "https://billing.stripe.com/p/session/test_..."
}
FieldTypeDescription
portal_urlstringHosted Stripe Customer Portal URL. Redirect the user here.

Errors

StatusCodeWhen
400no_subscriptionThe authenticated user has no Stripe customer ID yet (never reached checkout).
401unauthorizedMissing or invalid JWT.

GET /platform/v1/billing/status

Return the current plan, payment-method status, plan limits, and today's usage counters for the authenticated user.

Authentication: JWT required.

Request

GET /platform/v1/billing/status
Authorization: Bearer <jwt_token>

Response

200 OK

{
"plan": "starter",
"currency": "usd",
"stripe_subscription_id": "sub_1NXY...",
"stripe_customer_id": "cus_NXY...",
"limits": {
"max_jobs_per_day": 10000,
"max_projects": 3,
"rate_limit_per_hour": 5000
},
"usage": {
"jobs_today": 142,
"project_count": 2
}
}
FieldTypeDescription
planstringActive plan slug: free, starter, pro, business.
currencystringISO-4217 currency code for the active plan (currently always "usd").
stripe_subscription_idstring | nullStripe Subscription ID, or null when on the free plan.
stripe_customer_idstring | nullStripe Customer ID, or null if checkout has never been completed.
limits.max_jobs_per_dayintegerDaily job-create quota for the plan.
limits.max_projectsintegerMaximum project count allowed. Returns -1 for unlimited tiers (pro, business) — treat any negative value as "no project cap".
limits.rate_limit_per_hourintegerPer-tenant request rate limit.
usage.jobs_todayintegerJobs created so far today (UTC) across all of the user's projects.
usage.project_countintegerCurrent number of projects owned by the user.

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.

GET /platform/v1/billing/invoices

Return the most recent invoices for the authenticated user, ordered newest first. Backed by the Stripe Invoices list API.

Authentication: JWT required.

Request

GET /platform/v1/billing/invoices?limit=20
Authorization: Bearer <jwt_token>

Query parameters

ParameterTypeDefaultDescription
limitinteger20Number of invoices to return. Must be between 1 and 100 inclusive.

Response

200 OK

{
"data": [
{
"id": "in_1NXY...",
"status": "paid",
"amount_total": 2900,
"currency": "usd",
"stripe_subscription_id": "sub_1NXY...",
"pdf_url": "https://pay.stripe.com/invoice/.../pdf",
"hosted_invoice_url": "https://invoice.stripe.com/i/...",
"created_at": "2026-04-01T12:00:00Z"
}
]
}
FieldTypeDescription
idstringStripe Invoice ID.
statusstringStripe invoice status: draft, open, paid, uncollectible, void.
amount_totalintegerTotal amount charged in the currency's smallest unit (e.g. cents).
currencystringISO-4217 currency code for the invoice.
stripe_subscription_idstring | nullThe subscription this invoice belongs to.
pdf_urlstring | nullDirect download link for the PDF receipt.
hosted_invoice_urlstring | nullStripe-hosted HTML view of the invoice.
created_atstring (ISO 8601)When the invoice was created in Stripe.

Errors

StatusCodeWhen
400invalid_limitlimit is explicitly out of the 1..100 range.
401unauthorizedMissing or invalid JWT.

Plans

PlanPrice/mo (USD)Jobs/monthOpt-in overageProjectsRate limit/hr
Free$050,00011,000
Starter$29300,000$1.50 / 10k jobs35,000
Pro$993,000,000$1.00 / 10k jobsUnlimited50,000
Business$29930,000,000$0.50 / 10k jobsUnlimited500,000
EnterpriseCustomCustomCustomUnlimitedCustom

Job allowances are monthly, aligned to your billing period. When the allowance is reached, new job creates return 429 monthly_allowance_exceeded — unless opt-in overage is enabled for the project, in which case jobs continue and the extra volume is metered at the plan's per-10k rate. Set a spend cap to bound monthly overage charges; once the cap is reached, new job creates return 429 spend_cap_reached until the next period.

Tax

VAT and sales tax are collected automatically for EU and UK customers via Stripe Tax. No configuration is required on your side — Stripe determines the applicable rate from the billing address you enter during checkout.

  • VAT receipts — every invoice includes a full tax breakdown line. Download the PDF receipt from the Stripe Customer Portal (POST /platform/v1/billing/portal).
  • Tax IDs — VAT-registered organisations can enter their VAT/GST number at checkout. Stripe validates the ID in real time and applies the zero-rate B2B rule (reverse charge) where applicable.
  • Supported regions — EU member states, United Kingdom, Australia, and any other jurisdictions covered by Stripe Tax.

Currency

The currency field on GET /platform/v1/billing/status returns the ISO-4217 code for the active plan. All plans currently bill in usd. Dashboard price displays read this field so future multi-currency support requires no frontend change.

See also

  • Account API — read the authenticated user's account and delete-account flow
  • Errors402 payment_required, subscription_inactive, and other billing-gated failures
  • Rate Limits — quota tiers correspond to billing plans