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:- 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.
- 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.
- 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.
- 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.
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 ofPATCH /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:
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:
"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 withchange-version:
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 returns404 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_periodorcurrency_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 thereasonofsubscription.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, andpast_duesubscriptions move;change-versionrejects the others with409 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-versionwithperiod_endreturns409 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 withmove, 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:
When a move at renewal applies
A move at renewal waits for the subscription’s next renewal, the datescheduledVersionChange.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}/moveslists the plan’s moves, andGET /plans/{id}/moves/{moveId}reports each subscriber asmoving,moved,needs_a_change,left_out,canceled, orkept_on_version.- A change of one subscription with
change-versionis recorded as a move of that one subscriber. It appears inGET /plans/{id}/movesand the Moves tab like any other move. POST /plans/{id}/moves/{moveId}/cancelcancels the move for everyone who hasn’t moved yet.POST /plans/{id}/moves/{moveId}/subscriptions/{subscriptionId}/cancelcancels it for one.DELETE /subscriptions/{id}/scheduled-version-changerevokes one subscription’s pending move.
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 withPOST /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:
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
SendplanVersionId 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.
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 returns409 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.