Billing API
Create checkout/portal sessions and manage subscriptions via the Emberly API.
Cloud only. Billing runs on embrly.ca and these endpoints return 404 on self-hosted instances see Cloud vs. Self-Hosted.
- Requires a session cookie (authenticated browser session), not an upload token these power the dashboard's upgrade/billing flows and aren't intended for external integrations.
- Requires Stripe to be configured on the server if it isn't, they return
501(or503for sync) instead of processing anything.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /api/payments/checkout | Start a subscription checkout session |
GET | /api/payments/checkout | Same, via query param (?priceId=) |
GET | /api/payments/portal | Redirect to the Stripe customer portal |
POST | /api/payments/purchase | Start a one-off purchase (extra storage, custom domain) |
GET | /api/payments/purchase | Same, via query param |
GET | /api/payments/promo-codes | List active, publicly visible promo codes |
POST | /api/payments/sync-subscription | Re-sync your Stripe subscriptions into the database |
Start a Subscription Checkout
POST /api/payments/checkout
Body:
successUrl/cancelUrl are optional and default to /dashboard and /pricing. metadata must be a flat object of string-to-string pairs — anything else is silently dropped.
Response (200):
Redirect the user to url to complete checkout. Any existing account credit is applied automatically.
Errors: 401 unauthenticated · 501 Stripe not configured · 404 user not found
Open the Billing Portal
GET /api/payments/portal
No body. Responds with an HTTP redirect straight to the Stripe customer portal, where the user can update payment methods, view invoices, or cancel.
Errors: 401 unauthenticated · 501 Stripe not configured
One-Off Purchase
POST /api/payments/purchase
For non-subscription add-ons — extra storage or an extra custom domain slot.
Body:
type is extra_storage or custom_domain (informational — stored in Stripe metadata, doesn't change server behavior). quantity defaults to 1.
Response (200):
Errors: 401 unauthenticated · 501 Stripe not configured · 404 user not found
List Promo Codes
GET /api/payments/promo-codes
Public-safe list of currently active promotion codes, for display on the pricing page. No authentication required. Codes with metadata.private = "true" in Stripe are excluded.
Response (200):
If Stripe isn't configured or the Stripe API call fails, this returns an empty array rather than an error — promo codes are a display-only enhancement.
Sync Subscription
POST /api/payments/sync-subscription
Re-pulls your active Stripe subscriptions into Emberly's database. Useful if a checkout succeeded but the webhook that should have recorded it was missed or hadn't been configured yet.
Response (200):
If you have no linked Stripe customer yet, it responds with { "synced": 0, "message": "No Stripe customer linked" } instead of erroring.
Errors: 401 unauthenticated · 503 Stripe not configured
Webhook
POST /api/payments/webhook receives Stripe webhook events (checkout completion, subscription updates/cancellations, invoice events) and is called by Stripe directly, not by clients — it's listed here for completeness only.