Subscriptions
A subscription puts a customer on a plan and recurs on the plan’s interval until something changes. Everything else — invoices, payments, dunning, revenue recognition — descends from this one object.
States
pending_activation ──► trialing ──► active ──► past_due ──► canceled │ │ │ ▼ ▼ ▼ active paused canceled │ └──► active| State | Meaning |
|---|---|
pending_activation | Created, awaiting trial start or first invoice. |
trialing | In a free trial. No invoices issued yet. |
active | Healthy. Invoices issuing on schedule. |
past_due | An invoice payment failed; in dunning. |
paused | Manually or programmatically paused. Auto-resumes on paused_until. |
canceled | Terminal. Will not bill again. |
ended | Terminal. Reached its commitment end without renewal. |
incomplete_expired | Terminal. Initial activation never completed. |
The full transition table lives in the Status & lifecycle reference.
Creating a subscription
POST /v1/subscriptionsContent-Type: application/jsonIdempotency-Key: <uuid>
{ "customer_id": "<customer uuid>", "plan_id": "<plan uuid>", "payment_method_id": "<payment method uuid, optional>", "trial_period_days": 14, "start_at": "2026-06-01T00:00:00Z"}If trial_period_days is set (or the plan has a default trial), the
subscription starts in trialing. Otherwise it transitions to active
immediately and the first invoice is issued. start_at is optional —
omit it to start now.
Versioned mutations
Every lifecycle mutation below is versioned: pass the subscription’s
current version as a query parameter (?version=3) and an
Idempotency-Key header. A stale version returns 409; re-fetch the
subscription and retry with the fresh value.
Billing cycles
A plan bills weekly, monthly, quarterly, semi_annual or annual.
The calendar cycles bill on the subscription’s billing anchor day,
derived from the activation timestamp (surfaced as billing_anchor_day
on the subscription); an anchor of the 31st lands on the last day of
shorter months. Use start_at at creation to align renewals to, say,
the 1st of the month.
A weekly period is exactly 7 days in UTC and ignores the anchor day.
When a weekly subscription moves to a monthly or longer plan, the anchor
resets to the day the new plan starts.
Failed payments retry 3, 8 and 15 days after the first failure (wider in a few slower-settling currencies). Weekly subscriptions retry after 1, 3 and 6 days instead, so dunning finishes before the next week is charged.
When a cycle ends, the invoice engine:
- Computes per-component charges (flat / per-unit / tiered / etc).
- Aggregates usage events for any
usagecomponents. - Applies discounts (subscription-level, then plan-level, then promo).
- Computes tax via the tax engine.
- Sums to a total, finalises the invoice, and triggers payment.
Changing the plan
POST /v1/subscriptions/{id}/change-plan?version={version}Idempotency-Key: <uuid>{ "new_plan_id": "<plan uuid>" }One endpoint handles every change; the routing depends on the direction
of the move. The response’s change_kind tells you which path was taken.
Upgrades apply immediately (change_kind: "immediate"):
- Same cycle, higher value — the plan swaps in place and the prorated
differential for the rest of the period is charged now
(
proration_invoice_id,proration_amount_minor). - Cross-cycle upgrade — a longer calendar cycle (e.g. monthly →
annual), or a move to or from
weeklythat costs more per year (e.g. $500/month → $500/week). The current period ends now: unused advance-billed time comes back as a credit note (credit_note_id), a fresh period starts immediately under the new cycle, and the new period’s in-advance invoice is issued.
Downgrades are scheduled (change_kind: "scheduled"):
- Same cycle, lower value, a shorter calendar cycle (e.g. annual
→ monthly), or a move to or from
weeklythat costs less per year (e.g. $500/week → $500/month) — the customer keeps the plan they paid for until the current period ends; the swap applies automatically at the period roll. The response carriesscheduled_plan_idandeffective_at; the subscription exposesscheduled_plan_iduntil then. No money moves at scheduling time. - To revert a scheduled change, call change-plan again with the plan
the subscription is currently on (
change_kind: "unscheduled"). An upgrade posted while a downgrade is queued applies immediately and clears the queue.
Between the calendar cycles, a longer commitment counts as the upgrade
whatever the price, so monthly → annual applies now even when annual is
discounted. A week is not a shorter commitment than a month in that
sense, so changes to or from weekly compare annualized prices instead
(weekly × 52, monthly × 12, quarterly × 4, semi-annual × 2); equal
annualized prices fall back to cycle length.
You can change plans as many times as you like within a billing period.
Immediate changes each settle their own prorated differential;
previous_plan_id / plan_change_at reflect the most recent applied
change and reset at the period roll.
Constraints:
- The new plan must share the subscription’s currency
(
412 subscription_plan_currency_mismatch). - The subscription must be
trialing,active, orpast_due— except longer-cycle switches, which are blocked whiletrialing(412 subscription_plan_change_trialing).
Events: subscription.plan_change_scheduled when a change is queued,
subscription.plan_change_unscheduled when it’s reverted, and
subscription.plan_changed whenever the plan actually changes (both
immediate changes and roll-applied scheduled ones).
Pause / resume
POST /v1/subscriptions/{id}/pause?version={version}Idempotency-Key: <uuid>{ "paused_until": "2026-08-01T00:00:00Z" }paused_until is optional — omit it to pause indefinitely; when set it
must be in the future. Resume early with
POST /v1/subscriptions/{id}/resume?version={version}.
The subscription-maintenance worker auto-resumes the subscription on
paused_until and emits subscription.resumed.
Cancellation
Immediate, with proration:
POST /v1/subscriptions/{id}/cancel?version={version}Idempotency-Key: <uuid>{ "reason": "customer_request" }The period is sealed at now. If a finalized invoice already covered the
full period, a credit note is issued for the unused portion
(credit_note_id); otherwise a final prorated invoice is created for the
used portion (final_invoice_id).
Or at the period boundary:
POST /v1/subscriptions/{id}/cancel-at-period-end?version={version}Idempotency-Key: <uuid>This sets cancel_at_period_end: true and emits
subscription.cancel_scheduled; the subscription continues until the
current period closes.
reason is free-form audit text carried on the subscription.canceled
event.
Common events
| Event | When it fires |
|---|---|
subscription.created | Object created (any state). |
subscription.updated | Quantity / metadata / other field changed. |
subscription.plan_changed | The plan changed — immediately, or at the roll for scheduled changes. |
subscription.plan_change_scheduled | A downgrade / shorter-cycle switch was queued for the period end. |
subscription.plan_change_unscheduled | A queued plan change was reverted before it applied. |
subscription.trial_ended | Trial ended; entered active or past_due. |
subscription.paused | Entered paused. |
subscription.resumed | Returned to active from paused. |
subscription.cancel_scheduled | Cancellation scheduled for period end. |
subscription.canceled | Entered canceled. |
subscription.expired | Reached end of life; entered ended. |
See the event catalog for the complete list.
Where to next
- Trials & commitments — modelling trial periods and minimum-term commitments.
- Create a subscription — the integration recipe.