Beacon

Billing API

View as Markdown

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

EndpointWhen to useKey requirements
POST /api/billing/subscription/checkout?org={slug}&mode={test|live}start a new subscription or upgradeBeacon bearer token, OWNER/ADMIN, plan, and billingCycle
POST /api/billing/subscription/confirm?org={slug}&mode={test|live}reconcile final state after checkout returnorganization context; optional sessionId in the body
POST /api/billing/subscription/portal?org={slug}&mode={test|live}open self-service plan/payment managementmapped Stripe customer and eligible subscription
  1. 11) Create checkout

    Create a session with plan, billing cycle, and organization context. Persist checkoutSessionId.

  2. 22) Redirect user

    Send the user to redirectUrl and prevent concurrent submits in the frontend.

  3. 33) Confirm state

    After the checkout return, call subscription/confirm. If you have the success URL session_id, pass it as sessionId.

  4. 44) Expose portal safely

    Generate portal sessions only once a manageable Stripe customer/subscription exists.

Checkout creation example

bash
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"
  }'
json
{
  "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/confirm and Stripe events.

Example confirmation

bash
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"}'
json
{
  "ok": true,
  "subscription": {
    "plan": "PLUS",
    "status": "ACTIVE"
  },
  "context": {
    "mode": "test",
    "organizationSlug": "acme"
  }
}

Frequent errors

CodeTypical causeRecommended action
400invalid org, plan, or billingCyclefix payload/query and retry
401invalid/expired tokenrenew the Beacon session and retry
403caller is not OWNER/ADMINreview organization permissions
409active subscription, pending checkout, or session not ready yetreuse existing state or wait a few seconds
404organization missing or inactiveconfirm slug and active context

Go-live checklist

  • Full test flow passes in mode=test with no pending states.
  • Alerts configured for confirmation failures and retry spikes.
  • Portal access restricted by role with audit trail.