Billing API
Endpoints for subscription management, Stripe checkout, and webhook processing.
Plans and Pricing
| Plan | Monthly | Annual (per month) | Annual (total) |
|---|---|---|---|
| Pro | $19/mo | $15/mo | $180/yr |
| Studio | $79/mo | $63/mo | $756/yr |
Create Checkout Session
Create a Stripe Checkout session to subscribe to Pro or Studio.
POST /v1/billing/checkout
Authorization: Bearer <jwt>Body:
{
"plan": "pro",
"cycle": "monthly"
}| Field | Values | Description |
|---|---|---|
plan | pro, studio | Target subscription plan |
cycle | monthly, annual | Billing cycle |
Response: 200 OK
{
"data": {
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_..."
}
}Redirect the user to checkout_url to complete payment. After successful payment, Stripe sends a webhook to activate the subscription.
Customer Portal
Open the Stripe Customer Portal for managing payment methods and billing history.
POST /v1/billing/portal
Authorization: Bearer <jwt>Response: 200 OK
{
"data": {
"portal_url": "https://billing.stripe.com/p/session/..."
}
}Get Subscription
GET /v1/billing/subscription
Authorization: Bearer <jwt>Response: 200 OK
{
"data": {
"tier": "pro",
"status": "active",
"billing_cycle": "monthly",
"current_period_start": "2024-01-15T00:00:00Z",
"current_period_end": "2024-02-15T00:00:00Z",
"cancel_at_period_end": false
}
}Subscription Statuses
| Status | Description |
|---|---|
active | Subscription is active and paid |
trialing | User is on a 14-day Pro trial |
past_due | Payment failed — subscription is in grace period |
canceled | Subscription canceled — access retained until period end |
Upgrade
Upgrade from Pro to Studio. The remaining balance is prorated.
POST /v1/billing/upgrade
Authorization: Bearer <jwt>Body:
{
"plan": "studio",
"cycle": "monthly"
}Response: 200 OK
{
"data": {
"tier": "studio",
"status": "active",
"effective_immediately": true
}
}Upgrades take effect immediately. The prorated amount for the remaining days on the current plan is credited toward the new plan.
Downgrade
Downgrade from Studio to Pro. Takes effect at the end of the current billing period.
POST /v1/billing/downgrade
Authorization: Bearer <jwt>Body:
{
"plan": "pro"
}Response: 200 OK
{
"data": {
"tier": "studio",
"downgrade_to": "pro",
"effective_at": "2024-02-15T00:00:00Z"
}
}INFO
You retain Studio features until the end of the current billing period. The downgrade is applied automatically when the period ends.
Cancel
Schedule cancellation at the end of the current billing period.
POST /v1/billing/cancel
Authorization: Bearer <jwt>Response: 200 OK
{
"data": {
"tier": "pro",
"cancel_at_period_end": true,
"access_until": "2024-02-15T00:00:00Z"
}
}You retain access to your current tier's features until access_until. After that, entitlements revert to the Core tier.
Webhook Processing
Stripe sends webhook events to the billing service for asynchronous payment processing.
POST /v1/billing/webhooksThis endpoint is called by Stripe, not by clients. It verifies the webhook signature using the webhook signing secret.
Handled Events
| Event | Action |
|---|---|
checkout.session.completed | Activate subscription, update entitlements, send welcome email |
invoice.payment_succeeded | Update current_period_end, log payment |
invoice.payment_failed | Mark subscription past_due, send payment failure email |
customer.subscription.updated | Sync tier changes (upgrade/downgrade) |
customer.subscription.deleted | Revert to Core tier, update entitlements |
Idempotency
Each webhook event has a unique event.id. The API tracks processed event IDs to ensure idempotent handling — processing the same event multiple times produces the same result.
Retry Behavior
If webhook processing fails, the API returns HTTP 500 to trigger Stripe's automatic retry mechanism (exponential backoff for up to 3 days).
Subscription Lifecycle
Account Created → Core
↓
Start Trial → Pro (14 days)
↓ ↓
Trial Expired Subscribe
↓ ↓
Core Pro / Studio
↓
Upgrade / Downgrade
↓
Cancel → Core (at period end)