Skip to content

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.


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 }.

Terminal window
curl https://api.pandastack.io/v1/billing/status \
-H "Authorization: Bearer $PANDASTACK_TOKEN"

GET /v1/billing/info

Billing info including the active subscription period.

Auth: any member (card-gate allowlisted). Response (BillingInfo):

FieldTypeNotes
planfree | pro | premium | enterprise
cardVerifiedboolean
hasStripeCustomerboolean
subscriptionStatusstring | null
periodStartstring | null
periodEndstring | null

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 }.

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:

FieldTypeNotes
idnumber
invoiceIdstring | nullStripe invoice id.
amountnumber | nullDollars.
statusstring | nullpaid | pending | failed | cancelled.
typestring | nullsubscription | usage | free.
datestring | null
planNamestring | null
descriptionstring | null
pdfUrlstring | null
hostedUrlstring | null
Terminal window
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 }.


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 }.

Terminal window
curl -X POST https://api.pandastack.io/v1/billing/setup-intent \
-H "Authorization: Bearer $PANDASTACK_TOKEN"

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 }.

POST /v1/billing/portal

Create a Stripe Customer Portal redirect session (manage cards, invoices, subscription).

Auth: admin. Body: none. Response (BillingPortal): { url }.


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 }.

Terminal window
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.

POST /v1/billing/cancel-subscription

Cancel the Stripe subscription and reset the org to Free.

Auth: owner. Body: none. Response (CancelResult): { downgraded, plan }.


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.