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
| Method | Endpoint | Notes |
|---|---|---|
| POST | /v1/billing/admin/apps | Create an app |
| GET | /v1/billing/admin/apps | List apps |
| POST | /v1/billing/admin/apps/:app_id/secrets | Per-app Apple/Google/Stripe/webhook secrets |
| POST | /v1/billing/admin/products | Upsert {app_id, store, store_product_id, product_type, …} |
| GET | /v1/billing/admin/products?app_id=… | List products |
| POST | /v1/billing/admin/entitlements | Upsert; accepts product_ids: [] |
| GET | /v1/billing/admin/entitlements?app_id=… | List entitlements |
| POST | /v1/billing/admin/offerings | Upsert with nested packages; setting is_current=true clears the flag on the app's other offerings |
| POST | /v1/billing/admin/offerings/:offering_id/audience | Attach an audience to an offering |
| POST / GET | /v1/billing/admin/audiences | Upsert / list audiences |
| POST / GET | /v1/billing/admin/experiments | Upsert / list experiments |
| POST | /v1/billing/admin/experiments/:id/status | Set draft, running, paused, or completed |
| GET | /v1/billing/admin/experiments/:id/results | Variant impressions, conversions, and z-score |
| POST / GET | /v1/billing/admin/paywalls | Upsert / get paywall config |
| POST | /v1/billing/admin/paywalls/:offering_id/publish | Promote draft to live config |
| POST | /v1/billing/admin/grant | Manually grant an entitlement (promo / refund recovery): {app_id, app_user_id, product_id, duration_days} |
| POST | /v1/billing/admin/webhook-endpoints | Register an outbound listener |
Customer
| Method | Endpoint | Notes |
|---|---|---|
| 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/attributes | Merge JSONB attributes |
| POST | /v1/billing/customers/:app_user_id/alias | Merge 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/impression | Record experiment impression |
| POST | /v1/billing/customers/:app_user_id/experiments/conversion | Record 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.
| Method | Endpoint | Notes |
|---|---|---|
| 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
| Method | Endpoint | Notes |
|---|---|---|
| GET | /v1/billing/offerings?app_id=… | Fetch the current offering for a paywall (unchanged for backwards compatibility) |
| GET | /v1/billing/plans | List all subscription plans (no auth, no app_id filter) |
Purchase flow
A typical client-initiated purchase, from tap to unlocked feature:
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.