Plan changes in a pricing plan group

Move a subscription from one plan to another in the same pricing plan group in a single call, with the timing decided by the group rather than by the caller.


Why this matters

An upgrade from Starter to Pro is one commercial decision. As a billing operation it is several: end the running schedule on the right date, open a new one on the target plan, keep the currency and billing period, and get the pro-ration right. Doing that by hand means the same sequence of calls every time. The date it lands on is then whatever the caller passed, rather than what the plan family says it should be.

A pricing plan group already holds that policy: which plans belong together, in what order, and when a move between them takes effect. change-plan executes it. An upgrade becomes one request, and the timing rules live with the plans instead of in your integration.

How it works

A group carries an ordered list of member plans and three transition policies:

FieldWhat it decides
pricing_plans[].orderThe position of a plan within the group. It is what makes one move an upgrade and the opposite a downgrade
upgrade_typeWhen a move to a higher-ordered plan takes effect
downgrade_typeWhen a move to a lower-ordered plan takes effect
cancellation_typeDeclared on the group for cancelling a member plan. This action does not read it; cancelling a subscription is its own operation
statusA move is only accepted while the group is ACTIVE

The move itself is a new schedule on the same subscription, pointing at the target plan. The schedule that was running is closed at the moment the new one starts, so the subscription still has exactly one schedule in effect.

Direction is inferred, never passed. A target with a higher order than the current plan is an upgrade and follows upgrade_type; a lower one is a downgrade and follows downgrade_type. That policy then resolves the date:

PolicyStart of the new schedulestart_at on the request
IMMEDIATENow, or the start_at you sendAllowed
IMMEDIATE_PRO_RATANow, or the start_at you sendAllowed
NEXT_BILLING_PERIODThe subscription’s next invoice date, in the account’s time zoneRejected
NOT_ALLOWEDThe transition is refusedRejected

IMMEDIATE and IMMEDIATE_PRO_RATA resolve to the same start moment. What follows from that start is the subscription’s own pro-ration behaviour: whether the closed period is charged in full or pro-rated. The two policies describe that behaviour rather than change it.

Change the plan

Send the target plan. Everything else is either inferred or carried over:

Upgrade a subscription to another plan in its group
curl -X POST https://test.api.solvimon.com/v1/pricing-plan-subscriptions/ppsu_jwDeeN0tYSY3F7BkeN1v/change-plan \
-H "X-API-KEY: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"pricing_plan_id": "ppla_QwDeeN0vcYJaBLAc0C29"
}'
Response
{
"object_type": "PRICING_PLAN_SCHEDULE",
"id": "ppsc_hwDeeN0vcYJaBLAc0C1W",
"pricing_plan_subscription_id": "ppsu_jwDeeN0tYSY3F7BkeN1v",
"pricing_plan_version_id": "ppve_2PnBx7VdtRgLmE4WcQJ8K",
"status": "ACTIVE",
"start_at": "2026-10-01T00:00:00Z",
"billing_period": { "type": "MONTH", "value": 1 },
"pricing_currency": "EUR"
}
FieldRequiredDescription
pricing_plan_idYesThe plan to move to. A member of the same group as the subscription’s current plan
start_atNoOnly on an IMMEDIATE or IMMEDIATE_PRO_RATA policy. Defaults to now, and cannot be in the past
billing_periodNoChange the billing period along with the plan. Must be one of the target version’s billing period settings. Absent keeps the current one

The response is the new schedule, returned with 201 Created. See Change the pricing plan of a subscription in the API reference. The call needs both PRICING_PLAN_SUBSCRIPTION.UPDATE and PRICING_PLAN_SCHEDULE.CREATE, since it updates a subscription by creating a schedule on it.

What the new schedule takes with it

Carried overNot carried over
The pricing currency, from the current schedule or the subscription, when the target version has currency settingsOverride pricings
The billing period, from the request, the current schedule or the subscription, when the target version has billing period settingsCommitments
The invoice date realignment setting of the schedule it replacesDiscounts
Seat values

Anything negotiated onto the schedule being replaced is customer-specific pricing on a plan the customer is leaving. It is not copied onto the plan they are moving to. Configure it again on the new schedule if it still applies.

A target plan that is a rate card works the same way. The version applicable at the new schedule’s start is resolved, and the schedule keeps following the rate card rather than being pinned to that version.

Change only the billing period

A plan sold in several billing periods, monthly and yearly against the same pricing, is moved between them by sending the plan the subscription is already on together with a different billing_period:

Move a subscription from monthly to yearly billing
curl -X POST https://test.api.solvimon.com/v1/pricing-plan-subscriptions/ppsu_jwDeeN0tYSY3F7BkeN1v/change-plan \
-H "X-API-KEY: <apiKey>" \
-H "Content-Type: application/json" \
-d '{
"pricing_plan_id": "ppla_hwDeeN0vcYJaBLAc0C1W",
"billing_period": { "type": "YEAR", "value": 1 }
}'

There is no order difference to read the direction from here, so it comes from the length of the period instead. A longer billing period is an upgrade, a shorter one a downgrade. The group’s policy for that direction then decides the timing as usual. Two periods of the same length, MONTH/12 against YEAR/1, carry no direction and are rejected.

Show the group in the customer portal

A self-service upgrade screen has to know which plans it may offer. That is exactly the group’s member list, and the portal exposes it for the subscription the session is scoped to:

Read the group behind a subscription
curl https://test.api.solvimon.com/v1/portal/pricing-plan-subscriptions/ppsu_jwDeeN0tYSY3F7BkeN1v/pricing-plan-group \
-H "Authorization: Bearer <portalAccessToken>"

The response is the group itself. It carries the members with their order, plus the upgrade_type, downgrade_type and cancellation_type. Those tell the screen which moves are possible, and when each would take effect.

The portal endpoint is read-only. Performing the change is a platform-side call, so the customer’s action reaches change-plan through your backend, not from the browser. For the changes a customer can make to their own subscription directly, see Self-serve plan changes.

What is rejected

RejectedFieldWhy
The subscription is not ACTIVEresource_idThere is no running schedule to replace
A later schedule already exists on the subscriptionresource_idThe change truncates the last schedule, and truncating one that has a successor would leave a gap or an overlap
The current plan is in no group, or the group is not ACTIVEpricing_plan_idThere is no policy to apply
The target plan is not a member of that grouppricing_plan_idA move outside the group is a migration, not a plan change
The same plan with no billing_periodpricing_plan_idNothing would change
The direction’s policy is NOT_ALLOWEDpricing_plan_idThe group forbids that move
start_at on a NEXT_BILLING_PERIOD policystart_atThe date comes from the invoice schedule, not from the caller

Via Desk

There is no dedicated plan change action in Desk yet. An agent making the same move edits the subscription’s schedules directly, which the group’s policies do not bind. The policies exist to keep an automated or self-service change on the terms the plan family was sold on.

Edge cases

  • The move is not a migration. Migrating a schedule moves it to a different version of the plan it is already on. change-plan moves the subscription to a different plan. That is why it builds a new schedule instead of remapping the existing one.
  • start_at cannot be in the past. The subscription also has to be active at that moment, so a date before its start is rejected.
  • Only billing subscriptions. A quote subscription is rejected: agreed terms change through a new quote version, not through this action.
  • The group is resolved from the current plan, not from the request. If the subscription’s plan was removed from its group, the call fails. It does not fall back to the target’s group.