Billing API
The billing endpoints manage the org’s Stripe customer, its card, and its subscription. This route group is allowlisted past the card wall so an unverified org can add a card and view plans. Card-mutating and subscription actions require the owner role.
Concept guides: Plans & limits, Instance tiers, Usage & metering.
All routes below require authentication except the webhook, which verifies a Stripe signature.
Billing status
Section titled “Billing status”GET /v1/billing/status
Read-only billing status: plan and whether a card is on file.
Auth: any member (card-gate allowlisted). Response (BillingStatus): { plan (free|pro|premium|enterprise), cardVerified, hasStripeCustomer }.
curl https://api.pandastack.io/v1/billing/status \ -H "Authorization: Bearer $PANDASTACK_TOKEN"Billing info
Section titled “Billing info”GET /v1/billing/info
Billing info including the active subscription period.
Auth: any member (card-gate allowlisted). Response (BillingInfo):
| Field | Type | Notes |
|---|---|---|
plan | free | pro | premium | enterprise | |
cardVerified | boolean | |
hasStripeCustomer | boolean | |
subscriptionStatus | string | null | |
periodStart | string | null | |
periodEnd | string | null |
Stripe config
Section titled “Stripe config”GET /v1/billing/config
Public-safe Stripe config (the publishable key) used by the add-card flow.
Auth: any member (card-gate allowlisted). Response: { publishableKey }.
Invoices
Section titled “Invoices”GET /v1/billing/invoices
Invoice history, live from Stripe (falls back to the legacy Payments table).
Auth: any member (card-gate allowlisted). Response: Invoice[].
Invoice:
| Field | Type | Notes |
|---|---|---|
id | number | |
invoiceId | string | null | Stripe invoice id. |
amount | number | null | Dollars. |
status | string | null | paid | pending | failed | cancelled. |
type | string | null | subscription | usage | free. |
date | string | null | |
planName | string | null | |
description | string | null | |
pdfUrl | string | null | |
hostedUrl | string | null |
curl https://api.pandastack.io/v1/billing/invoices \ -H "Authorization: Bearer $PANDASTACK_TOKEN"GET /v1/billing/plans
List purchasable subscription plans.
Auth: any member (card-gate allowlisted). Response: PlanOption[] — each { id, name, monthlyUsd, isFree, purchasable }.
Create a SetupIntent
Section titled “Create a SetupIntent”POST /v1/billing/setup-intent
Create a Stripe SetupIntent to add/verify a card. Returns a client secret for Stripe.js.
Auth: owner. Body: none. Response (SetupIntentResult): { clientSecret }.
curl -X POST https://api.pandastack.io/v1/billing/setup-intent \ -H "Authorization: Bearer $PANDASTACK_TOKEN"Complete card setup
Section titled “Complete card setup”POST /v1/billing/setup-intent/complete
Complete card setup, flipping card_verified on success — this clears the card wall.
Auth: owner. Body: none. Response (CardVerifyResult): { verified }.
Open the customer portal
Section titled “Open the customer portal”POST /v1/billing/portal
Create a Stripe Customer Portal redirect session (manage cards, invoices, subscription).
Auth: admin. Body: none. Response (BillingPortal): { url }.
Subscription
Section titled “Subscription”Upgrade
Section titled “Upgrade”POST /v1/billing/upgrade-subscription
Start a Stripe Checkout Session to upgrade. The subscription is activated by the Stripe webhook after checkout completes.
Auth: owner. Body (UpgradeInput): { planId } (positive int). Response (UpgradeResult): { requiresCheckout, checkoutUrl, sessionId }.
curl -X POST https://api.pandastack.io/v1/billing/upgrade-subscription \ -H "Authorization: Bearer $PANDASTACK_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "planId": 2 }'{ "success": true, "data": { "requiresCheckout": true, "checkoutUrl": "https://checkout.stripe.com/…", "sessionId": "cs_test_…" }}Redirect the user to checkoutUrl. Activation happens asynchronously via the Stripe webhook.
Cancel / downgrade to Free
Section titled “Cancel / downgrade to Free”POST /v1/billing/cancel-subscription
Cancel the Stripe subscription and reset the org to Free.
Auth: owner. Body: none. Response (CancelResult): { downgraded, plan }.
Stripe webhook
Section titled “Stripe webhook”POST /webhook
Receives Stripe events. It is not token-authenticated — it verifies the stripe-signature header (via constructWebhookEvent) over the raw request body. Do not call this yourself or send an Authorization header; it is configured as your Stripe webhook endpoint.
Processing is idempotent. On success it returns 200 { "received": true }; on a processing failure it returns 500 to force Stripe to retry. The response is not wrapped in the standard envelope.
{ "received": true }This webhook is what activates a subscription after upgrade-subscription, and it drives the billing DB writes behind GET /v1/billing/info and GET /v1/billing/invoices.
Related
Section titled “Related”- Usage & limits API — allowances that a plan upgrade raises.
- Authentication — the card hard-wall.
- Integrations API — GitHub events webhook — the other signature-verified receiver.