Skip to main content

Documentation

REST API

Admin, customer, and public routes for Stackhouse-Billing.

REST API

All JSON. Admin routes require a service-admin JWT; customer routes require any authenticated user; inbound store-webhook routes are unauthenticated but signature-verified.

Conditionally mounted

The entire /v1/billing/* router only exists when the server is started with STACKHOUSE_ENABLE_BILLING set (checked in main.rs). If that env var isn't set, none of the routes on this page are reachable — there's no /v1/billing/health to hit, not a 404 vs. 200 distinction, the router itself is never nested into the app.

Admin

MethodEndpointNotes
POST/v1/billing/admin/appsCreate an app
GET/v1/billing/admin/appsList apps
POST/v1/billing/admin/apps/:app_id/secretsPer-app Apple/Google/Stripe/webhook secrets
POST/v1/billing/admin/productsUpsert {app_id, store, store_product_id, product_type, …}
GET/v1/billing/admin/products?app_id=…List products
POST/v1/billing/admin/entitlementsUpsert; accepts product_ids: []
GET/v1/billing/admin/entitlements?app_id=…List entitlements
POST/v1/billing/admin/offeringsUpsert with nested packages; setting is_current=true clears the flag on the app's other offerings
POST/v1/billing/admin/offerings/:offering_id/audienceAttach an audience to an offering
POST / GET/v1/billing/admin/audiencesUpsert / list audiences
POST / GET/v1/billing/admin/experimentsUpsert / list experiments
POST/v1/billing/admin/experiments/:id/statusSet draft, running, paused, or completed
GET/v1/billing/admin/experiments/:id/resultsVariant impressions, conversions, and z-score
POST / GET/v1/billing/admin/paywallsUpsert / get paywall config
POST/v1/billing/admin/paywalls/:offering_id/publishPromote draft to live config
POST/v1/billing/admin/grantManually grant an entitlement (promo / refund recovery): {app_id, app_user_id, product_id, duration_days}
POST/v1/billing/admin/webhook-endpointsRegister an outbound listener

Customer

MethodEndpointNotes
GET/v1/billing/customers/:app_user_id?app_id=…Fetch customer
GET/v1/billing/customers/:app_user_id/entitlements?app_id=…Resolved entitlements
POST/v1/billing/customers/:app_user_id/attributesMerge JSONB attributes
POST/v1/billing/customers/:app_user_id/aliasMerge two customer identities
POST/v1/billing/customers/:app_user_id/receipts/apple{app_id, receipt_data}
POST/v1/billing/customers/:app_user_id/receipts/google{app_id, package_name, subscription_id, purchase_token, access_token?}
POST/v1/billing/customers/:app_user_id/receipts/stripe{app_id, event}
GET/v1/billing/customers/:app_user_id/offerings/resolve?app_id=…Resolve per-user offering, experiment, and paywall
POST/v1/billing/customers/:app_user_id/experiments/impressionRecord experiment impression
POST/v1/billing/customers/:app_user_id/experiments/conversionRecord experiment conversion

Checkout & subscription management (JWT required)

Stripe Checkout flow, distinct from the store-receipt customer routes above — these act on the authenticated caller (AuthedUser), not an arbitrary :app_user_id path param.

MethodEndpointNotes
POST/v1/billing/checkout{app_id, price_id, app_user_id, customer_email?, success_url, cancel_url} — creates a Stripe Checkout session (mode=subscription); requires STRIPE_SECRET_KEY or STACKHOUSE_STRIPE_SECRET_KEY set on the server
POST/v1/billing/cancel{app_id, app_user_id} — cancels the caller's active Stripe subscription at period end
GET/v1/billing/me?app_id=…Returns {customer, subscriptions, entitlements} for the authenticated user

Public

MethodEndpointNotes
GET/v1/billing/offerings?app_id=…Fetch the current offering for a paywall (unchanged for backwards compatibility)
GET/v1/billing/plansList all subscription plans (no auth, no app_id filter)

Purchase flow

A typical client-initiated purchase, from tap to unlocked feature:

Rendering diagram…

See Entitlements & Security for how is_active is computed, Experiments & Paywalls for A/B and paywall routes, and Webhooks for the inbound store-notification routes.