Skip to content

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
StateMeaning
pending_activationCreated, awaiting trial start or first invoice.
trialingIn a free trial. No invoices issued yet.
activeHealthy. Invoices issuing on schedule.
past_dueAn invoice payment failed; in dunning.
pausedManually or programmatically paused. Auto-resumes on paused_until.
canceledTerminal. Will not bill again.
endedTerminal. Reached its commitment end without renewal.
incomplete_expiredTerminal. Initial activation never completed.

The full transition table lives in the Status & lifecycle reference.

Creating a subscription

POST /v1/subscriptions
Content-Type: application/json
Idempotency-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:

  1. Computes per-component charges (flat / per-unit / tiered / etc).
  2. Aggregates usage events for any usage components.
  3. Applies discounts (subscription-level, then plan-level, then promo).
  4. Computes tax via the tax engine.
  5. 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 weekly that 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 weekly that 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 carries scheduled_plan_id and effective_at; the subscription exposes scheduled_plan_id until 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, or past_due — except longer-cycle switches, which are blocked while trialing (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

EventWhen it fires
subscription.createdObject created (any state).
subscription.updatedQuantity / metadata / other field changed.
subscription.plan_changedThe plan changed — immediately, or at the roll for scheduled changes.
subscription.plan_change_scheduledA downgrade / shorter-cycle switch was queued for the period end.
subscription.plan_change_unscheduledA queued plan change was reverted before it applied.
subscription.trial_endedTrial ended; entered active or past_due.
subscription.pausedEntered paused.
subscription.resumedReturned to active from paused.
subscription.cancel_scheduledCancellation scheduled for period end.
subscription.canceledEntered canceled.
subscription.expiredReached end of life; entered ended.

See the event catalog for the complete list.

Where to next