Skip to main content
Every subscription is pinned to one version of its plan. A version holds the complete terms: prices, trials, features, credit and balance rules, add-ons, credit packs, and country prices. The plan points at its current version, which every new buyer gets. Editing a plan publishes a new version and moves nobody. Existing subscribers keep the prices, features, and rules of their version, at renewal too, until you move them.

What a version holds

Edit details changes the name, description, and visibility at once, for everyone. Market prices apply to every version: an edit to a Market price reaches the subscribers who pay it at their next renewal. The plan’s versions are numbered 1, 2, 3. An experiment’s own versions carry its code, such as price-test 2.

Edit in the dashboard

Open a plan from Plans. The version menu opens the current version, drafts, experiments, and older versions. The plan page has six tabs:
  1. Edit any version. The first change starts a draft from it. A draft is saved as you edit, and nobody sees it until you publish it. A removed price, feature, add-on, or credit pack stays in place, struck through, with Undo. Disabling a feature appears in the changes as Disabled; only removing it appears as removed.
  2. The plan header shows the draft’s number of changes. Open it to review Changes in this draft and undo any of them, or choose Discard draft.
  3. Choose Review and publish in the header, then publish it as:
    • The current version, which new buyers get.
    • A new experiment, sold only through its own code. Only for drafts started from the current version.
    • Next version of an experiment, for drafts started from an experiment’s latest version.
  4. Choose what happens to subscribers of earlier versions: Keep them on their version, Move them at their next renewal, or Move them now. When some of them don’t fit the new version, the dialog lists each one with what would make it fit.
A draft publishes exactly its content. A draft started from an older version undoes what later versions changed, and the review lists those changes. Publishing stops while a country is missing a price, or when the current version would drop the trial without a payment method of the plan group’s signup trial. To move subscribers later, open Subscribers, select them, and choose Move to another version.

Edit through the API

Every plan edit made through the API, MCP, or the CLI publishes a new current version at once and moves nobody. That includes adding, changing, or removing prices and features, regional pricing, and the credit and balance rules of PATCH /plans/{id}. New buyers get the new version; existing subscribers stay on theirs until you move them. To prepare several changes and publish them together, start a draft:
The draft starts from the current version unless you send fromVersionId. Change it with PATCH /plans/{id}/drafts/{draftId} and the blocks prices, countries, rules, and undo. Each block you send replaces that block, so prices lists every interval the draft sells:
Publish it with "preview": true first to check everything without publishing:
Features and the add-ons and credit packs a plan sells are not part of an API draft: their own endpoints publish a new version at once. Introductory Offers are set in a dashboard draft. POST /plans/{id}/versions publishes a new current version without a draft. It changes only money terms: prices, included credits or balance, per-currency amounts, and the carry-over cap.

Move subscribers

Only you move subscribers, and a move changes a subscription’s version, never its plan. Move one subscription with change-version:
Repeating the same request changes nothing, and a new one replaces the pending move. Move many at once with POST /plans/{id}/moves, sending subscriptionIds (up to 10,000) or fromVersionId to move every subscription of that version:
A move never prorates or refunds. With now, a larger included amount first pays any plan credit or balance debt, a smaller one never creates new debt, and purchased credits and balance are untouched.

What a move keeps

  • Currency, country, interval, and price. A move keeps a subscription’s currency unless you ask to charge it in USD. A version that doesn’t sell its currency returns 422 missing_regional_price, and one that doesn’t sell the price it pays returns 404 price_not_found.
  • Charge in USD. When the version doesn’t sell the currency a subscriber pays in its country, or the USD price of its country, send currency: "usd" with the move: from its next renewal it pays the version’s USD price for its country, or the USD base price. The currency changes only at a renewal, even for a move made now, and the usage of the period that closes is charged at the USD base rates of the version it leaves. Nothing is converted: when something keeps the subscription in its currency, the move is refused with a code that names it: currency_held_by_payment_method (it pays through dLocal), currency_held_by_customer_credit, currency_held_by_balance_plan, currency_held_by_fixed_amount_discount, currency_held_by_prepaid_period or currency_held_by_scheduled_pause. The same is checked again at the renewal; if it no longer fits, the move is canceled with that code as the reason of subscription.version_change_canceled.
  • Market prices. A subscription that pays a Market price keeps paying it.
  • Free or paid. A free version moves only to a free version, and a paid one only to a paid one (400 validation_error).

Who can move

  • Subscriptions move only to a newer version (422 version_not_newer). One on a running experiment moves only to a later version of that experiment.
  • Only active, trialing, and past_due subscriptions move; change-version rejects the others with 409 invalid_subscription_status. A plan move leaves out the others, such as paused ones, and listed subscriptions already on the destination; they stay on their version. A scheduled cancellation, pause, downgrade, or interval change doesn’t leave a subscription out.
  • A one-time subscription never renews, so it moves only now. change-version with period_end returns 409 invalid_subscription_status, and a plan move at renewal leaves it out.
  • An active add-on the version doesn’t sell (409 addon_unavailable), or more seats than the version allows (400 seat_limit_exceeded), keeps a subscription from moving until you resolve it.

Resolve conflicts

A plan move, a publish with move, and an experiment archive check every subscriber first. If any of them doesn’t fit, nobody moves and the response is 409 subscribers_do_not_fit, listing each one with the reasons. Send a change for each one in changes:
A subscriber with a change always moves at its renewal, even in a move made now.

When a move at renewal applies

A move at renewal waits for the subscription’s next renewal, the date scheduledVersionChange.effectiveAt shows. The renewal invoices the new period on the new version and sends one subscription.version_changed. A subscription that ends instead of renewing ends on its version, whether by a cancellation at the end of its period or trial, exhausted dunning, the suspension of the organization, or an immediate cancellation. It sends no version webhook, scheduledVersionChange becomes null, and its plan move stops waiting for it. A change to another plan, now or at that renewal, such as a scheduled downgrade or the plan group’s default plan, lands on the other plan’s current version, also without a version webhook. While a cancellation or a downgrade is scheduled, the API and the Subscribers tab still show the move with its date. If the cancellation or downgrade is revoked, the subscription moves at its renewal.

Track and cancel moves

  • GET /plans/{id}/moves lists the plan’s moves, and GET /plans/{id}/moves/{moveId} reports each subscriber as moving, moved, needs_a_change, left_out, canceled, or kept_on_version.
  • A change of one subscription with change-version is recorded as a move of that one subscriber. It appears in GET /plans/{id}/moves and the Moves tab like any other move.
  • POST /plans/{id}/moves/{moveId}/cancel cancels the move for everyone who hasn’t moved yet. POST /plans/{id}/moves/{moveId}/subscriptions/{subscriptionId}/cancel cancels it for one.
  • DELETE /subscriptions/{id}/scheduled-version-change revokes one subscription’s pending move.
Canceling or revoking a pending move sends subscription.version_change_canceled with reason: "canceled_by_merchant" for each subscriber it cancels, and records them as canceled. A move scheduled for renewal is checked again at the renewal. If the subscription no longer fits, it renews on its version and appears in GET /plans/{id}/needs-a-change and under Needs a change on the Subscribers tab. Each entry carries the moveId of the move it couldn’t complete. Move it again with a change, or leave it with POST /plans/{id}/needs-a-change/{subscriptionId}/keep.

Run an experiment

An experiment sells a version only to the buyers you send to it; everyone else keeps buying the current version. Publish a draft started from the current version as A new experiment, or create one with POST /plans/{id}/experiments. Its code uses lowercase letters, numbers, hyphens, and underscores, and is never reused in the plan. Sell it by sending the experiment code after the plan code:
An unknown experiment code returns 404 plan_experiment_not_found. Making an experiment current through the API moves nobody else. The dashboard also asks what happens to subscribers of earlier versions.

Sell a specific version

Send planVersionId on POST /subscriptions to create the subscription on any published version, current or not, including an experiment’s versions. Checkout charges that version’s price, and activation grants its credits, add-ons, and credit packs. Sending planVersionId with an experiment code returns 400.

Unpaid checkouts

A checkout that hasn’t been paid isn’t a subscriber yet, so it follows the latest version of what was requested: Its link charges that version without a new request. Repeating the same POST /subscriptions after you edit the plan reuses the checkout: the response is 200 with the same subscription ID and checkoutUrl. A different request replaces it: another plan, experiment, version, interval, or price, the plan code after an experiment code, or planVersionId instead of a code or the other way round. The old subscription is canceled, its invoice is voided, and its link stops working.

Reactivate a canceled subscription

Reactivating a canceled subscription returns it on the latest version of what was bought:
  • the plan’s current version, when it was bought with the plan code;
  • the experiment’s latest version, when it was bought with an experiment code, or the plan’s current version once the experiment is no longer running;
  • the same version, when it was bought with planVersionId.
Add-ons that version doesn’t sell are deactivated. A version that doesn’t sell the subscription’s currency returns 422 missing_regional_price. Reactivating a past_due subscription keeps its version.

Add-ons, credit packs, and countries

Each version lists the add-ons and credit packs it sells, with their prices. Creating or editing an add-on or credit pack publishes a new version of each plan that sells it. A subscriber buys only what its version sells: an add-on or pack added in a newer version returns 409 addon_unavailable or 409 credit_pack_unavailable until you move the subscriber. See Add-ons and Credit Packs. Each version also sets countries with their own currency and prices. See Set prices by country.

Read versions

Versions published before Commet recorded every term are not listed, and reading one by ID returns 404 plan_version_not_found. Every subscription includes planVersion, with its id and label, and scheduledVersionChange, with the planVersionId, label, and effectiveAt of the move pending for its next renewal, or null. See When a move at renewal applies. Filter GET /subscriptions by version with planVersionId.

Webhooks

Neither is sent when the subscription ends or changes to another plan before the move applies, or when a new move replaces the pending one. Both reach only the webhook endpoints that list them. Add them to your endpoint’s events to receive them.