Change Subscription Plan
Moves a subscription onto a plan, effective at its next renewal.
Two behaviors, one endpoint
Which one you get depends on whether the subscription is already on a plan.CANCELED with a
cancellation_source of PLAN_CHANGE, and the replacement inherits the original’s
remaining billing cycles and its next renewal date — availability.start_at and
billing_cycles.next_at on the replacement both equal the original’s next_at, and its
billing_cycles.current restarts at 1. If that renewal date has already passed (for
example on a long-paused subscription), the replacement instead starts about a minute from
now and bills right away.
Webhooks
A plan-to-plan change emits three events:subscription.createfor the replacement.subscription.cancelfor the original, withcancellation_source: PLAN_CHANGE. Do not count it as churn.subscription.plan_changedfor the replacement, withprevious_subscription_idset.
TRIALING on this call.
Skipping the target plan’s trial
If the target plan defines trial or intro phases, the subscription adopts that whole ladder by default — which means a subscriber can change plans mid-trial and receive a fresh trial. Setskip_trial to true to prevent that. The replacement bills the target plan’s regular
price from its first charge and its status stays ACTIVE rather than becoming TRIALING.
skip_trial defaults to false and applies only to plan-to-plan changes. Sending
skip_trial: true on a subscription that is not on a plan returns 400 — false and an
omitted flag are equivalent and are accepted everywhere. On a plan with no phases it makes no
difference.
phases still describes the planWith skip_trial: true the response still lists the target plan’s phases — that array
describes the plan, not what this subscription will be billed. The subscription-level
signals are status, which stays ACTIVE instead of becoming TRIALING, and the absence
of current_phase and billing_phases: both keys are omitted from the response rather than
returned as null, so test for absence, not for null. A default change returns both
populated.Scheduling the change instead
Both mechanisms take billing effect at the next renewal — neither charges now. They differ in what happens to the subscription record. This endpoint restructures it immediately: an attach mutates the subscription in place, and a plan-to-plan change returns a newid with the
original left CANCELED. The plan_change object on
Update Subscription leaves the subscription
untouched until the renewal and keeps the same id.
plan_change is not a general substitute: it requires a subscription that is already on a
plan and is ACTIVE or TRIALING. Attaching a plan to a subscription that has none, or
changing plans from CREATED or PAUSED, is only possible through this endpoint.
The two differ in their trial default, for backwards-compatibility reasons:
skip_trial or phases_behavior explicitly whenever the trial behavior matters to you.
Repeated calls
While the subscription is in an accepted status, re-posting its current plan is an idempotent no-op that returns200 with the subscription unchanged — including when you
change only skip_trial, and including when that plan has since been canceled.
A plan-to-plan change is not repeatable on the same subscription_id. Once it succeeds the
original is CANCELED, so re-posting to that id returns 400 INVALID_STATE
(The subscription status CANCELED does not support plan changes.) rather than replaying the
first outcome. Treat that error as confirmation the change already landed, and point any
follow-up call at the replacement id.
If a call times out, reconcile with GET /v1/subscriptions/{subscription_id} instead of
retrying blind: the original carries cancellation_source: PLAN_CHANGE once the change
landed, and the replacement is the subscription whose previous_subscription_id is that
id. An in-flight retry sent while the original is still changeable is safe — it is
deduplicated as long as skip_trial is unchanged. A different skip_trial value is a
different operation.
Requirements
The subscription must not beCANCELED or COMPLETED — those return 400 INVALID_STATE
(The subscription status <STATUS> does not support plan changes.). This endpoint accepts
CREATED and PAUSED subscriptions, which the plan_change object on Update Subscription
does not.
Both cases require the plan to be ACTIVE and to belong to the subscription’s account, and
neither is available to subscriptions billed with frequency.execution: MERCHANT.
Attaching a subscription that has no plan additionally requires that the plan’s cadence
matches the subscription’s and that the resolved price is in the subscription’s currency. The
subscription must not be in a trial period, must not already have billing phases, must not use
a billing_date, and must not have a VARIABLE amount.
The subscription adopts the target plan’s phase ladder from its next unbilled cycle, and that
ladder must fit inside the subscription’s remaining billing cycles. If the ladder opens with a
TRIAL phase covering the cycle in flight, an ACTIVE subscription becomes TRIALING on this
same call; CREATED and PAUSED subscriptions are not promoted this way. skip_trial cannot
suppress that — it is a plan-to-plan-only flag.
Changing from one plan to another additionally requires that the subscription has at least
one billing cycle left and that its availability window does not end before the next renewal.
The target plan must accept the subscription’s payment-method type, if it restricts
allowed_payment_methods, and the subscription must have a stored vaulted payment method. The
target plan’s cadence and price apply as defined on that plan.
If the subscription had a scheduled change pending, this call discards it without a
subscription.plan_change_canceled webhook; the replacement starts with
pending_plan_change: null.
Errors
200 no-op. The same intent expressed
through the plan_change object on Update Subscription
is rejected with 422 SAME_PLAN.Authorizations
Path Parameters
The unique identifier of a subscription.
Body
The unique identifier of the plan to move the subscription onto.
"1904911d-5df9-429e-8488-ad41abea1a4d"
Plan-to-plan changes only. When true, the replacement subscription does not adopt the target plan's trial or intro phases: it bills that plan's regular price from its first charge and its status stays ACTIVE instead of becoming TRIALING. Use it to stop subscribers from earning a fresh trial by switching plans. Sending true on a subscription that is not on a plan returns 400; false and an omitted flag are equivalent and always accepted.
true
Response
200