Plans
A plan is the unit a customer subscribes to. It bundles one or more products with their prices and per-product allowances ("Pro Monthly", "Enterprise Annual", etc.). Subscriptions never attach to the live, mutable plan - they pin to a specific plan version, an immutable snapshot of the plan's contents at publish time.
Plan vs plan version
| Object | Mutable? | What it carries |
|---|---|---|
Plan | Yes (name, description, status, attached products) | The long-lived identifier, edited freely while in draft |
PlanVersion | No, once published | Frozen snapshot: products, their pricing models, the available PlanProductPriceSummary rows (price ID + currency + billing interval + list price + optional price_key), per-product allowances |
Every subscription stores plan_version_id, so changing the live plan never silently mutates anyone's bill - only an explicit migration can move subscribers between versions.
Lifecycle
Code
| Status | What changes |
|---|---|
draft | Free editing - attach / detach products, change name + description, no subscriber sees this. |
published | The first publish creates version 1; later publishes increment. New subscriptions can attach. |
deprecated | Soft-warning state - existing subscribers keep working; tooling discourages selecting this plan for new subscriptions. |
archived | Hard stop - no new subscriptions, no further publishes. Existing subscribers continue to bill from their pinned version. |
Status transitions are made via PUT /v1/plans/{id} with the new status, except the first draft → published transition which goes through POST /v1/plans/{id}/publish (it also creates the version snapshot).
Plan products & allowances
A plan attaches products via POST /v1/plans/{id}/products and detaches via DELETE /v1/plans/{id}/products/{productId}. Each attachment (PlanProduct) can carry per-period allowance fields:
| Field | Notes |
|---|---|
included_quantity | Quantity included in the base plan price before usage starts being charged. For keyed products, the allowance is pooled across all price_keys. |
rollover_enabled | If true, unused included_quantity from one period is added to the next. Emits allowance.rolled_over on each rollover. |
rollover_max | Hard cap on how much can accumulate from rollovers. |
rollover_expiry_periods | Rolled-over balance expires after this many periods. |
The plan version snapshot also carries price_key_label per product (frozen at publish), so the invoice pipeline can detect "this product was keyed when the plan was published" without re-querying the live product.
Versions: publish, clone, compare
Code
clone is the right call when you want to ship a price-changed plan as a separate offer (so existing subscribers stay where they are by default and only opt-in customers see the new one). publish + migrate-subscribers is the right call when you want everyone moved to the new version.
Migrating subscribers
POST /v1/plans/{id}/migrate-subscribers previews or executes a bulk move of a plan's subscribers from their current pinned version to a target version.
Code
| Field | Behaviour |
|---|---|
mode | PREVIEW returns the planned-change report without mutating anything; IMMEDIATE executes now; SCHEDULED creates a pending migration that runs at scheduled_at and emits notice events leading up to it. |
target_version | The version subscribers are moved to. Must be ≥ 1 and exist on the plan. |
proration_strategy | Optional. Controls how the in-flight billing period is settled when the migration runs. |
scheduled_at | Required when mode = SCHEDULED; ignored otherwise. |
The migration is all-or-nothing per plan - there's no per-subscription-ID filter on this endpoint. To move a single subscription, change its plan_version_id directly via the Subscriptions endpoints. On execute, the platform fires plan.subscribers_migrated.
Scheduling other plan changes
For changes that aren't subscriber migrations (e.g. status moves, attached-product edits scheduled for a future cutover), use the version-backed scheduler:
Code
These return scheduled changes you can list, cancel, or query the resolved timeline of.
Per-plan dunning
Each plan can carry its own dunning policy via /v1/plans/{planId}/dunning-policy - overrides the org-wide default. See Dunning.
Endpoints
Related
- Products - the catalog units plans bundle
- Subscriptions - the entity that pins to a plan version
- Pricing models - the per-product tier-walk algorithms
- Advanced pricing - keyed prices, customer overrides
- Scheduled changes - the underlying mechanism for
POST /plans/{id}/schedule - Dunning - per-plan overrides