Subscriptions
A subscription connects a customer to a plan version and bills it on a repeating cycle. It is where the catalog stops being a catalog and starts producing invoices.
It carries the things that vary per customer: the quantities of each item, the currency, the billing interval, the day of the month the cycle turns over, and whether it renews. It also pins the plan version it was created on, so the version it bills is a recorded fact rather than "whatever the plan says today".
Think of it as the contract. Almost everything on this page is a consequence of that: the currency is fixed for its whole life, the plan version is written down, the period boundaries are stored rather than derived, and the only way out is cancellation.
Status, and phases
This trips people up constantly, so it is worth being exact. A subscription's status is three values; everything else you might call a status is a phase.
Its status has exactly three values: DRAFT, ACTIVE and
CANCELLED. There is no trialing status, no paused status and no dunning
status.
| From | Can become |
|---|---|
DRAFT | ACTIVE, CANCELLED |
ACTIVE | CANCELLED |
CANCELLED | nothing — it is terminal |
CANCELLED is the only ending there is, and it does not say why on its own.
cancellation_reason does: USER_REQUEST (somebody asked), AUTO_NON_RENEWAL
(the term ran out with auto_renew off), DUNNING_TERMINAL (the retry ladder gave
up) or AGREEMENT_TERM_ENDED (a committed term reached its agreed end). Reading
churn without reading the reason counts a term ending exactly as planned as if it
were a customer walking away.
Phases
Trialing, paused and dunning are phase kinds on the active subscription. A
subscription in a trial is ACTIVE with a phase of kind trial. One in dunning
is ACTIVE with a phase of kind dunning. Filtering for a "trialing
subscription" by status will find nothing.
A subscription is a chain of phases ordered by phase_order, and exactly one is
ACTIVE at a time. The rest are PENDING (planned, not yet reached),
COMPLETED (been and gone) or SKIPPED (never will be — cancelling a subscription
skips everything still pending).
The five phase kinds
The kind is not a label. It is a behaviour table the billing engine reads directly:
| Kind | What it means | Invoices? | Usage billed? |
|---|---|---|---|
setup | Before recurring billing starts. Onboarding, provisioning, an activation fee. | yes | yes |
trial | Free or reduced period at the start. | no | no |
standard | Normal billing. Most subscriptions live here permanently. | yes | yes |
paused | Billing suspended, subscription intact. | no | no |
dunning | Payment has failed and the retry policy is running. | yes | yes |
Usage events are still accepted during a trial or a pause, and a prepaid wallet can still be drawn against — they simply do not become billable charges. That is deliberate: analytics should not go blind because somebody paused.
At most one trial phase per subscription. A second one is refused, whether it arrives in the create request or is appended later.
How a phase ends
Each phase declares its own end policy:
end_kind | Ends when |
|---|---|
manual | Never by itself. Somebody has to move it on. |
duration | duration_value × duration_unit after it started. The unit is day, week, month or billing_cycle. |
date | At the absolute end_at instant. |
auto_transition then decides whether reaching that end starts the next pending
phase by itself, or whether it waits for
POST /v1/subscriptions/{id}/transition-phase. That endpoint refuses with 409
when there is no active phase, no pending successor, or the active phase has not
reached its end — pass {"force": true} to override the last of those, which is
how you end a trial early.
A trial's days are not billed as if they were standard days
When a trial hands over to a phase that does invoice, the billed window is re-anchored to the moment the trial ended, not to the moment the sweep noticed. The days a customer had for free stay free.
phases is required when you create a subscription
CreateSubscriptionRequest requires customer_id and phases. A
subscription with no phases is not a valid subscription, so decide up front
whether it starts in setup, trial or straight into standard.
Starting one
start_trigger decides what turns a DRAFT into an ACTIVE subscription:
start_trigger | Starts when |
|---|---|
immediate (default) | At creation. The subscription is born ACTIVE. |
manual | When somebody calls POST /v1/subscriptions/{id}/activate. |
start_date | On the start_at instant you supply. Required for this trigger, rejected for the others. |
on_checkout_complete | When the checkout session it is bound to completes. |
Anything other than immediate lands in DRAFT, with its first phase prepared but
not started. POST /activate works regardless of the trigger and overrides the
wait — it is the manual override, not just the manual path.
Activation is what starts the clock: it stamps started_at, opens the first
period, and — for a subscription billing in_advance whose opening phase invoices
at all — cuts the first invoice.
The currency contract
A subscription has its own currency, and it is the single most important thing to
understand about money on this page.
It is the contract. Every line of every invoice the subscription produces is
priced in it. It is fixed when the subscription is created and frozen afterwards —
there is no field on PATCH /v1/subscriptions/{id} that changes it and no endpoint
that moves it.
Where it comes from
At creation, the first of these that has a value wins:
currencyon the create request.- The customer's
preferred_currency. - The currency of the first price in the plan version's snapshot.
EUR.
Whatever it resolves to must be enabled for billing on the workspace's
currency allow-list, or the create is refused with 400 and
CURRENCY_NOT_ALLOWED on the currency field.
It does not follow the customer
Changing a customer's preferred_currency afterwards does not re-denominate
subscriptions that already exist. The preference is a default for objects created
next; the subscription's own column is what the biller reads. A customer moved from
EUR to USD keeps receiving EUR invoices for every EUR subscription they hold.
What happens when the catalog prices something else
If the catalog does not carry a price in the contract currency, the catalog price is converted into the contract currency — the invoice does not quietly go out in the catalog's currency. The line records the rate it used and the FX policy that picked it. Conversion needs a rate to exist for the pair; if none does, the resolution fails rather than guessing. See Exchange rates.
The one deliberate exception is a usage line bound to a wallet: it prices in the wallet's currency, because the usage was metered against that balance and the invoice settles back against it.
One invoice, one currency
A period whose charges resolve to more than one currency — for example usage pinned
to a USD wallet alongside recurring charges contracted in EUR — is refused with a
409 rather than summed. Align the charges, or keep the wallet in the contract
currency.
Moving a subscription to another currency
Cancel it and create a new one with the currency you want. A customer may hold only
one live subscription per plan — a second create for the same
(customer, plan) is refused as a duplicate — but a cancelled subscription no
longer occupies that slot, so the replacement can be created immediately, on the
same plan, for the same customer. Nothing about the customer needs to be deleted or
recreated.
Cancelling mid-period credits the unearned remainder, so do it at a period boundary unless you want that credit. The full walkthrough, including the wallet and the allow-list, is on Customers.
Billing periods
current_period_start and current_period_end are the authoritative window. They
are stored on the subscription, not derived on read, and each renewal advances them
by exactly one interval from the previous end.
The interval is billing_interval_unit (week, month or year) times
billing_interval_count, which defaults to 1. A plan-backed subscription must
match the plan's declared cadence; the count is bounded in any case, because a
longer period does not increase the amount billed for it.
In advance or in arrears
billing_timing decides when the invoice for a period is cut:
| Value | Behaviour |
|---|---|
in_arrears (default) | The renewal tick at period end invoices the period that just closed, then advances the window. |
in_advance | The period is invoiced at its start: the first one at activation, each subsequent one when the previous period closes. |
billing_timing is editable only while the subscription is DRAFT; once it has
left DRAFT the field is refused.
in_advance prices the window before its usage exists
An advance-billed invoice is cut before the period's usage has been metered, and the
per-period uniqueness rule means that usage is not billed later. in_advance is
faithful for purely recurring plans; metered ones should stay in_arrears.
The billing anchor day
billing_anchor_day records the day of the month the cycle is anchored to. It is
1–28; anything outside that is refused with a 400 rather than clamped, so an
anchor never falls on a day February does not have.
When the create request omits it, it resolves in this order: the organization's
subscriptions.default_billing_anchor_day setting if one is configured, otherwise
the day-of-month the subscription was created on, capped at 28. It is not editable
through the update endpoint.
What a renewal actually does
The renewal sweep, per subscription, in order:
- Make sure the period that just closed has a finalised invoice. If a draft is sitting there from an interrupted run, it is finalised as it stands rather than discarded — that draft may carry operator edits.
- Hold if earlier debt is open. If any earlier invoice for this subscription is still open and unpaid, the renewal stops: no new invoice, no advance. The subscription stays due and is retried on the next tick, and the dunning ladder is what eventually ends it. Without this, a customer who stopped paying would accrue an unbounded debt one period at a time.
- For
in_advance, mint the next period's invoice before advancing. - Advance the window by one interval.
Each of those steps commits separately, and the sweep resumes correctly from any point in between — a crash between generating and finalising leaves a draft that the next tick finalises, not a duplicate.
Auto-renew
auto_renew defaults to true (or to the organization's
subscriptions.default_auto_renew setting). With it off, the subscription is
cancelled when its current period closes, with cancellation_reason
AUTO_NON_RENEWAL — it does not linger in some expired state, because there is no
such state.
Changing what is billed
Items are the products and quantities the subscription bills. They live on the subscription itself, not on its phases.
| Change | Endpoint |
|---|---|
| Restate a quantity | PATCH /v1/subscriptions/{id}/items/{itemId} |
| Add a product mid-cycle | POST /v1/subscriptions/{id}/items |
| Remove one | DELETE /v1/subscriptions/{id}/items/{itemId} |
| Price the change first | POST /v1/subscriptions/{id}/simulate |
Add and remove both accept an ISO 8601 effective_at to schedule the change for a
future instant; a past one is rejected.
Proration
A mid-period change is settled by proration_mode:
| Mode | What it does |
|---|---|
pro_rata (default) | Charges or credits the difference, scaled by the unbilled remainder of the period. |
pay_in_full | Charges the whole difference as if the change had been there all period. |
do_not_charge | Books nothing. The new state simply applies from the next invoice. |
The engine works on period totals, not per-unit amounts: the price is resolved
once at the old quantity and once at the new one, so tiered, package and formula
pricing prorate correctly without the engine knowing anything about tiers. A removal
always credits pro rata even where the subscription's default is pay_in_full — a
removal cannot credit an unearned whole period.
Omitting the mode inherits the subscription's settings.default_proration_mode,
then the organization's, then pro_rata.
Simulate before you commit
POST /v1/subscriptions/{id}/simulate prices a candidate change set — added items,
removed items, a new plan, a quantity change, a version change — and returns the
proration and MRR delta without writing anything. It books the same math its
mutating counterparts do.
Plan versions
A subscription pins the plan version it was created on (plan_version, or
product_version for a subscription billed straight off a product). What it does
when a newer version is published depends on one field.
Tracking mode
version_track_mode | Behaviour on a new published version |
|---|---|
latest | The subscription is moved onto the new version, applying version_change_strategy. |
pinned | Nothing happens. The subscription keeps billing the version it holds until somebody moves it. |
The default is latest, so publishing can move live subscriptions
Unless the organization has configured otherwise, new subscriptions are created with
version_track_mode: "latest" and version_change_strategy: "immediate_prorate" —
so publishing a new plan version does move active subscribers that track it, and
prorates the difference. Create with "version_track_mode": "pinned" for a
subscription that must be grandfathered, or set the organization's
subscriptions.default_version_track_mode to pinned for the whole book.
GET /v1/plans/{id}/publish-impact tells you how many active subscriptions would
move before you publish. Run it.
version_change_strategy is the money policy, and latest cannot be set without
one:
| Strategy | When the move applies |
|---|---|
immediate_prorate | Now, with a proration. |
next_period | At the next renewal boundary. |
at_phase_change | When the active phase hands over. |
at_phase_change has nothing to fire on if the active phase is open-ended
(end_kind: "manual"), and a subscription in that state is held rather than moved.
Plan transitions & proration
Two different endpoints, and the distinction matters:
| You want to | Call |
|---|---|
| Move to another version of the same plan or product | POST /v1/subscriptions/{id}/change-version |
| Move to a different plan — upgrade, downgrade, cross-grade | POST /v1/subscriptions/{id}/transition-plan |
change-version requires a strategy and either a concrete target_version or
target_latest: true. To defer a version move to an arbitrary date, send
strategy: "immediate_prorate" with a future scheduled_at; that is the only
supported way to date-schedule a version bump, and it mints a guarded
scheduled change rather than a bare edit.
POST /v1/subscriptions/bulk-upgrade fans the same per-subscription move out over a
list, skipping pinned subscriptions unless you pass force: true.
Drift and deviation
Two read-only endpoints answer "how far has this subscription wandered":
GET /v1/subscriptions/{id}/drift— how many versions behind it is, what the latest version is, and which versions it could move to.GET /v1/subscriptions/{id}/deviation— every way it departs from the plan version it is pinned to: operator-overridden included quantities, quantities outside the plan's authored band, products the plan no longer carries. Derived on every read; nothing is stored.
An operator override — for instance
PUT /v1/subscriptions/{id}/products/{productId}/included-quantity — deliberately
survives a version bump. The subscription then reads as adjusted against the new
version rather than silently losing the deal that was negotiated for it.
Pausing, cancelling and withdrawing
| Action | Endpoint | What it does |
|---|---|---|
| Pause | POST /v1/subscriptions/{id}/pause | Opens a paused phase. Status stays ACTIVE; billing freezes. |
| Resume | POST /v1/subscriptions/{id}/resume | Closes the pause and returns to the phase it interrupted — not a fresh open-ended one, so a fixed term survives a pause. |
| Cancel | POST /v1/subscriptions/{id}/cancel | Ends it. |
| Withdraw | POST /v1/subscriptions/{id}/withdraw | The EU 14-day right of withdrawal, with a full refund. |
Cancellation is immediate, not scheduled. The status flips to CANCELLED there
and then, the active phase is completed, every pending phase is skipped, and
subscription.cancelled is emitted. For a voluntary mid-cycle cancellation the
unearned remainder of the period is credited so it lands on the final invoice, and a
refund is requested for a subscription that had billable items and was not in a
trial. To end something at a future date instead, record it as a
scheduled change.
Cancelling inside a committed term is the exception to both halves of that. An agreement can refuse the request outright, and a notice period can defer it to a date months away. See Committed terms below.
withdraw is narrower on purpose. It applies to a CONSUMER subscription still
inside the statutory window (Directive 2011/83/EU) and answers
NOT_ELIGIBLE_FOR_WITHDRAWAL when it does not.
Committed terms
An agreement is the negotiated deal recorded against a subscription: a term the customer is committed to, the rates that hold for it, and what happens when it runs out. It is created by signing a quote that carries an agreed term, and most subscriptions have none.
An agreement is not a phase, and the difference decides which one you should be reading. A phase says what the subscription is doing, and the billing engine reads it. An agreement says what was promised, and billing never reads it at all, so a subscription can pass through a trial, a pause and three renewals under one unbroken agreement.
Three operations can be governed by a live agreement: cancelling the
subscription, decreasing a quantity, and removing an item. By default an
agreement governs the first only. What happens when one is attempted inside the
term is its early_termination_gate:
| Gate | What the API does |
|---|---|
NONE | The operation proceeds, and nothing is recorded. |
WARN | The operation proceeds and the attempt is recorded against the agreement. This is the default. |
BLOCK | The operation is refused with 409 until an override_reason is supplied. |
override_reason travels in the body of POST /v1/subscriptions/{id}/cancel and
of PATCH /v1/subscriptions/{id}/items/{itemId}, and as a query parameter on
DELETE /v1/subscriptions/{id}/items/{itemId}. It is ignored where nothing
governs the operation, so a client can always send one, and where something does
it is recorded with the principal who sent it.
A notice period defers rather than refuses. The cancellation is accepted and booked as a scheduled change for the day the notice runs out, clamped to the term's end so notice never extends a deal past its own last day. Either way, a cancellation that takes effect inside the term ends the agreement early rather than letting it run out, which is what keeps it out of the churn bucket that means "the deal ran its course".
A committed subscription can refuse to be cancelled
POST /v1/subscriptions/{id}/cancel answers 409 CONFLICT when a BLOCK gate
governs it, carrying agreement_id, committed_until, operation and gate as
top-level members of the problem document. The code is the generic CONFLICT,
so branch on agreement_id being present rather than on the code, and retry with
an override_reason.
Subscribe in the dashboard
-
Open the subscriptions list
In the sidebar under Billing, click Subscriptions, or press G then S.
-
Start the subscription
Click Create Subscription, then pick the customer and the plan. The plan version is pinned at this moment.
Customer first, then plan -
Set quantities and billing
Items seed from the plan's Default Quantity for each product. Adjust anything the customer bought differently, then set the billing interval and the anchor day if it should not follow the start date.
-
Activate it
A subscription is created as
DRAFTand bills nothing. Activating is what starts the first cycle.
Subscribe with the API
Omit currency and it is resolved from the customer, then the plan's prices, then
EUR. Omit version_track_mode and the subscription tracks the latest published
plan version. Both are worth stating explicitly on anything contractual.
Preview before you commit
POST /v1/subscriptions/preview-prices returns what a subscription would be
charged without creating anything. On anything with tiers or promotions, run it
first.
Dashboard and API names
| In the dashboard | In the API |
|---|---|
| Status: Active / Draft / Cancelled | status: "ACTIVE" / "DRAFT" / "CANCELLED" |
| Phases tab | phases[], each with a phase_kind |
| Items tab | items[], product and quantity |
| Currency | currency, fixed at creation — the contract currency |
| Billing column | billing_interval_unit and billing_interval_count |
| Next Billing | current_period_end |
| Drift column | how far the subscription has diverged from its plan version — GET /subscriptions/{id}/drift |
| Adjusted column | it deviates from its plan version — GET /subscriptions/{id}/deviation |
| Version tracking | version_track_mode (pinned / latest) plus version_change_strategy |
| Change version | POST /subscriptions/{id}/change-version |
| Change plan | POST /subscriptions/{id}/transition-plan |
| Agreements tab | every agreement the subscription has been under |
| Committed term rail group | the live agreement, its end date and its gate |
| Why is this happening early? | override_reason on cancel, item update and item removal |
Where subscriptions go next
- An invoice appears at the end of each period, built from the items, usage and any promotions that apply.
- Change quantity with
PATCH /v1/subscriptions/{id}/items/{itemId}. Mid-period changes prorate automatically. - Cancel with
POST /v1/subscriptions/{id}/cancel, which ends the subscription immediately and credits the unearned part of the period rather than deleting anything. To end it on a future date, schedule the change instead. A subscription inside a committed term answers differently: it can refuse with409until you send anoverride_reason, and it can defer the ending until its notice period runs out.
Next step
Continue to Invoices to see what this subscription produces.
Reference
| Topic | When you need it |
|---|---|
| Currencies | The money wire format, and the customer-vs-contract currency model |
| Currency policy | Which currencies a workspace may bill and price in |
| Exchange rates | What happens when the catalog does not price the contract currency |
| Agreements | The negotiated term behind a subscription, and what it takes to leave early |
| Scheduled changes | Plan moves, quantity changes and cancellations that take effect later |
| Versioning | How plan and product versions are published, and what pins to what |
| Dunning | What happens to a subscription whose invoices stop being paid |
| Add-ons | Extra products on a single subscription, outside the plan |
| Usage | Metered items and how their quantity is arrived at |
| Subscriptions API | Every field, plus allowance, drift and prorations |