Billing API
Core endpoints for checkout creation, subscription confirmation, and customer portal.
reference • updated 2026-03-17
Goal
Operate the full recurring billing lifecycle: start checkout, confirm post-payment state, and provide self-service access with organization-level traceability.
Endpoints and operational contract
| Endpoint | When to use | Key requirements |
|---|---|---|
POST /api/billing/subscription/checkout?org={slug}&mode={test|live} | start a new subscription or upgrade | Beacon bearer token, OWNER/ADMIN, plan, and billingCycle |
POST /api/billing/subscription/confirm?org={slug}&mode={test|live} | reconcile final state after checkout return | organization context; optional sessionId in the body |
POST /api/billing/subscription/portal?org={slug}&mode={test|live} | open self-service plan/payment management | mapped Stripe customer and eligible subscription |
Recommended flow
11) Create checkout
Create a session with plan, billing cycle, and organization context. Persist
checkoutSessionId.22) Redirect user
Send the user to
redirectUrland prevent concurrent submits in the frontend.33) Confirm state
After the checkout return, call
subscription/confirm. If you have the success URLsession_id, pass it assessionId.44) Expose portal safely
Generate portal sessions only once a manageable Stripe customer/subscription exists.
Checkout creation example
curl -X POST "https://test.beacon.pt/api/billing/subscription/checkout?org=acme&mode=test" \
-H "Authorization: Bearer <beacon_session_token>" \
-H "Content-Type: application/json" \
-d '{
"plan": "plus",
"billingCycle": "monthly"
}'
{
"ok": true,
"checkoutSessionId": "cs_123",
"redirectUrl": "https://checkout.stripe.com/c/pay/cs_123",
"plan": "PLUS",
"billingCycle": "monthly",
"context": {
"mode": "test",
"organizationSlug": "acme"
}
}
Confirmation and consistency
- Checkout does not expose a public
Idempotency-Key; the Stripe idempotency key is generated server-side. - If a pending checkout already exists for the same plan and cycle, the API can reuse it and return
pending: true. - If a different pending checkout exists or the organization already has an active subscription, the API returns
409. - Do not assume success from the redirect alone; final state must be confirmed by
subscription/confirmand Stripe events.
Example confirmation
curl -X POST "https://test.beacon.pt/api/billing/subscription/confirm?org=acme&mode=test" \
-H "Authorization: Bearer <beacon_session_token>" \
-H "Content-Type: application/json" \
-d '{"sessionId":"cs_test_a1b2c3"}'
{
"ok": true,
"subscription": {
"plan": "PLUS",
"status": "ACTIVE"
},
"context": {
"mode": "test",
"organizationSlug": "acme"
}
}
Frequent errors
| Code | Typical cause | Recommended action |
|---|---|---|
400 | invalid org, plan, or billingCycle | fix payload/query and retry |
401 | invalid/expired token | renew the Beacon session and retry |
403 | caller is not OWNER/ADMIN | review organization permissions |
409 | active subscription, pending checkout, or session not ready yet | reuse existing state or wait a few seconds |
404 | organization missing or inactive | confirm slug and active context |
Go-live checklist
- Full test flow passes in
mode=testwith no pending states. - Alerts configured for confirmation failures and retry spikes.
- Portal access restricted by role with audit trail.