> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paygentic.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Plan versions

> Learn how a plan keeps a version for each set of prices, and how each subscription follows or keeps a version

A plan version is one fixed set of prices for a plan. When you change the prices of a plan,
Paygentic does not change the version that exists. It makes a new version. The old version keeps
the values that it had, so you can always see what you sold.

Each subscription has a version policy. The policy decides if the subscription moves to a new
version of its plan or stays on the version that it holds.

## Prerequisites

* An API key. See [API keys](/api-keys).

## What a version is

A version has a `versionNumber`. The first version of a plan is `1`. Each new version has the next
number.

A version holds a list of price lines. Each line has a `key` and a price. The key is the name of
the line. It stays the same when you change the price of the line, so you can follow one line
through all the versions of the plan.

A version never changes. A later edit to a price does not change the values in an older version.
The plan itself, such as its name, is not part of a version. If you change only the name of a plan,
the API changes the name and makes no new version.

Paygentic creates a version when you:

* create a plan with prices. The plan starts with version `1`.
* change the prices of a plan with `PATCH /v0/plans/{id}`.
* edit a price that a plan uses with `PATCH /v0/prices/{id}`. See
  [Editing prices](/platform/pricing/editing-prices).
* create a version yourself with `POST /v0/plans/{id}/versions`.

## The default version

One version of the plan is the default version. The default version is the version that a new
subscription bills from. `defaultVersionId` in the plan response shows it.

When Paygentic creates a version, it becomes the default version immediately. You can also make an older
version the default again. See [Roll back a plan](#roll-back-a-plan).

## Read the versions

```bash theme={null}
curl https://api.sandbox.paygentic.io/v0/plans/plan_a1b2c3d4e5f6g7h8/versions \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
```

The response lists the versions, newest first.

```json theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "pver_h8i9j0k1l2m3n4o5",
      "object": "plan_version",
      "versionNumber": 2,
      "status": "published",
      "publishedAt": "2026-04-01T09:30:00.000Z",
      "isDefault": true,
      "subscriptionCount": 3
    },
    {
      "id": "pver_u2v3w4x5y6z7a8b9",
      "object": "plan_version",
      "versionNumber": 1,
      "status": "published",
      "publishedAt": "2026-03-01T08:00:00.000Z",
      "isDefault": false,
      "subscriptionCount": 12
    }
  ],
  "pagination": { "limit": 10, "offset": 0, "total": 2 }
}
```

`subscriptionCount` is the number of subscriptions that were on the version when they were
created. It does not show which version each subscription bills from now.

To read the price lines of one version, use its number:

```bash theme={null}
curl https://api.sandbox.paygentic.io/v0/plans/plan_a1b2c3d4e5f6g7h8/versions/2 \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
```

The response has a `prices` list. Each item has the `key` of the line and the fields of the price.
Use the `key` to address the line in a [price interval](/platform/billing/price-intervals).

Only an account that can manage the plan can read its versions. A version can show pricing that
you have not yet released, so read-only access is not enough.

## Create a version

Most edits create a version for you. To create a version yourself, send the full list of prices that
the new version must hold. Make the prices first with `POST /v0/prices`.

```bash theme={null}
curl -X POST https://api.sandbox.paygentic.io/v0/plans/plan_a1b2c3d4e5f6g7h8/versions \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "basedOnVersionId": "pver_h8i9j0k1l2m3n4o5",
    "prices": [
      { "key": "pk_m4n5o6p7q8r9s0t1", "priceId": "price_b3c4d5e6f7g8h9i0" },
      { "key": "pk_s9t0u1v2w3x4y5z6", "priceId": "price_k1l2m3n4o5p6q7r8" }
    ]
  }'
```

The request names the full set. It does not name a change. The API compares each `key` with the
current default version:

* A key with a different price ID replaces the line.
* A key that is only in the request adds a line.
* A key that the current version has and the request does not have removes the line.

An item with no `key` adds a line with a key that the API makes. You can send a price ID alone
instead of an object.

Send `basedOnVersionId` to protect against an old read. If the default version is not the version
that you name, the API refuses the request with status 409. A price list that you built from an old
read then cannot remove a line that another person added. If you do not send it, the API writes the
set without a check.

The response is the new version. It is published, and it is the default version.

A version must have one price or more. The API refuses an empty list.

## What a new default version does to a subscription

When a new version becomes the default version, the version policy of each subscription decides
what happens.

| Policy | What the subscription does |
| - | - |
| `floating` | The subscription moves to the new default version. The plan's `versionTransition` decides when. See [When a version change takes effect](#when-a-version-change-takes-effect). |
| `pinned` | The subscription stays on the version that it holds. |

A subscription that you create without a `versionPolicy` is `floating`. All the subscriptions that
existed before the field was available are also `floating`.

A change of the default version never changes a period that the customer has been invoiced for.

### Choose a policy

Send `versionPolicy` when you create the subscription, or when you update it:

```bash theme={null}
curl -X PATCH https://api.sandbox.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6 \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "versionPolicy": "pinned" }'
```

The subscription response shows the `versionPolicy`. It also shows `planVersionId` and
`versionNumber`, which are the version that the subscription holds.

Use `pinned` when a customer must keep the prices that you agreed. The subscription then keeps
the prices, the entitlements, and the grants of its version, also when you roll the plan back.
Use `floating` when you want each customer to get your latest prices.

To move a `pinned` subscription to the default version, change its policy to `floating`. The
subscription then moves to the default version under the plan's `versionTransition`. It does not go
back to `pinned` by itself.

## When a version change takes effect

The `versionTransition` of a plan decides when a subscription moves to a new version. It has two
fields, `effectiveDate` and `proration`. Paygentic supports two values:

| `versionTransition` | What the move does |
| - | - |
| `{ "effectiveDate": "end_of_current_period", "proration": "none" }` | Everything changes at the end of the current billing period. This is the default. |
| `{ "effectiveDate": "immediate", "proration": "none" }` | Entitlements change at once. Prices change without proration, by their payment term. |

The API refuses every other value with status `400`.

Set the policy when you create a plan with `POST /v0/plans`, or change it with `PATCH /v0/plans/{id}`:

```bash theme={null}
curl -X PATCH https://api.sandbox.paygentic.io/v0/plans/plan_a1b2c3d4e5f6g7h8 \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "versionTransition": { "effectiveDate": "immediate", "proration": "none" } }'
```

Every plan response shows `versionTransition`. A plan that you create without it gets
`end_of_current_period`. A new policy applies only to moves that occur after the change. A move that
occurred before it keeps its dates.

### An immediate move

An immediate move has three effective times:

* **Entitlements and grants** change at the move. A `floating` subscription moves at the next
  billing run after the new default version is published. A migration moves at the request.
* **In-arrears prices**, fees and metered prices, change at the start of the current period. The
  invoice that closes the period bills the whole period at the new version.
* **In-advance prices** change at the end of the current period. The period that the customer has
  paid for is not billed again and gets no credit.

Example: a monthly subscription has the period 1 March to 1 April. On 15 March, the subscription
moves to a version that:

| Change | Result |
| - | - |
| adds the feature SSO | The customer has access to SSO from 15 March, with its full grant. |
| removes a feature | Access ends on 15 March, and its grant is voided. |
| adds premium support at €50 a month, in arrears | The invoice on 1 April bills €50 for March. |
| changes premium support from €50 to €60, in arrears | The invoice on 1 April bills €60 for March, on one line. |
| changes API calls from €0.01 to €0.008 a call | The invoice on 1 April bills all of March's calls at €0.008. |
| adds API calls at €0.01 a call | The invoice on 1 April bills the calls from 15 March. |
| removes premium support, in arrears | The invoice on 1 April has no line for it. |
| adds SSO at €30 a month, in advance | No line for March. The invoice for April bills €30. |
| changes the platform fee from €100 to €120, in advance | The March line stays at €100. April bills €120. |

A grant that the move changes for a feature that both versions have changes at its next reset. In
the example, a grant that goes from 1,000 to 2,000 calls counts March against 1,000 calls and April
against 2,000. Calls above the grant in March bill at the new rate.

Some in-arrears prices move at the end of the current period instead, on both sides, as in an
end-of-period move:

* a price whose payment term or billing cadence changes between the versions;
* a price whose line in the subscription already starts or ends inside the current period, for
  example after a [price interval](/platform/billing/price-intervals) edit or an end-of-period move
  that is still pending;
* a price with a line of the current period that is already invoiced.

### Costs of an immediate move

Know these costs before you choose `immediate`. If one of them is not acceptable, keep
`end_of_current_period`.

* One move has more than one effective time.
* An added in-advance fee gives access free of charge for the rest of the current period.
* In-arrears and in-advance prices act differently. An added in-arrears fee bills the whole current
  period. An added in-advance fee bills nothing for it.
* Usage of a removed in-arrears price before the move is not billed.
* A `floating` subscription uses one policy for each move, for its additions and its removals.

## Migrate one subscription

To move one subscription to a version that you choose, send the number of that version as `targetVersionNumber`.
The version can be newer or older than the version that the subscription holds.

To choose when the migration takes effect, send `timing`. `next_period` moves everything at the end
of the current period. `immediate` applies the rules of [an immediate move](#an-immediate-move). If
you do not send `timing`, the plan's `versionTransition` applies.

```bash theme={null}
curl -X POST https://api.sandbox.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/versionMigration \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "targetVersionNumber": 2 }'
```

```json theme={null}
{
  "subscription": { "id": "sub_z9a0b1c2d3e4f5g6", "versionPolicy": "pinned" },
  "migration": {
    "fromVersionNumber": 1,
    "toVersionNumber": 2,
    "effectiveAt": "2026-11-01T00:00:00.000Z",
    "entitlementsEffectiveAt": "2026-11-01T00:00:00.000Z",
    "changed": true
  },
  "lineItems": { "syncFailed": false }
}
```

The response above shows only some fields of `subscription`.

* `effectiveAt` is the end of the current billing period. With `next_period`, the subscription
  bills from the new version from that instant, and the current period does not change. With
  `immediate`, in-advance prices change at that instant.
* `entitlementsEffectiveAt` is when the entitlements change: `effectiveAt` with `next_period`, and
  the time of the request with `immediate`.
* A subscription with no price timeline yet moves at once. For this subscription, `effectiveAt` and
  `entitlementsEffectiveAt` are the time of the request.
* A migration always sets `versionPolicy` to `pinned`. A later default version does not move the
  subscription. To follow the default version again, send
  `PATCH /v0/subscriptions/{id}` with `{ "versionPolicy": "floating" }`.
* If the subscription holds that version already, nothing moves and `changed` is `false`. You can
  send the same request again safely.
* To cancel a migration before `effectiveAt`, migrate the subscription back to the version that it
  held.
* When the subscription changes, Paygentic sends a `subscription.updated.v0` webhook.

The API refuses these requests:

| Status | Why |
| - | - |
| `404` | The plan of the subscription has no published version with that number. |
| `409` | The subscription is not active or awaiting payment, or it ends before `effectiveAt`. |
| `409` | The move would change the meaning of a price or a feature. The response names the key or the feature. See [What a version change keeps](#what-a-version-change-keeps). |
| `409` | A price that the new version sells is already covered by an interval of the subscription that continues past `effectiveAt`. End that interval, or remove the line from the version. |
| `429` | Another request is changing the subscription. Try again after `Retry-After` seconds. |

## Roll back a plan

You can make any published version the default version, to a newer version or to an older
version. To go back to an older price set, make the older version the default:

```bash theme={null}
curl -X PATCH https://api.sandbox.paygentic.io/v0/plans/plan_a1b2c3d4e5f6g7h8/versions/1 \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "default": true }'
```

The rules of the version policy apply to a rollback in the same way. A `floating` subscription
goes back to version `1` from its next billing period. A `pinned` subscription does not change.
The older version can lack a feature that the pinned version has. The pinned subscription then keeps its entitlement and its grant for that feature.

If the version is already the default version, the request changes nothing.

## What a version change does not change

* **A billed period.** Paygentic never recalculates a period that it has invoiced. A new
  version changes only the periods that start after the change.
* **An invoice that exists.** An issued invoice is final. See [Invoices](/platform/billing/invoices).
* **A subscription that is `pinned`.** It changes only when you change its policy or migrate it.

## What a version change keeps

These rules apply to every version change: a `floating` subscription that follows a new default
version, and a migration.

* **A price that you set for one subscription.** If you changed the rate or the quantity of a line
  in a [price interval](/platform/billing/price-intervals), the subscription keeps your value on
  the new version. If the new version does not sell that line, the subscription keeps the line
  with your value.
* **A line that you removed from one subscription.** If both versions sell the line, the
  subscription does not get it again.
* **A price that you added to one subscription.** An added price continues after the change, with
  its `endDate` if it has one.

Paygentic refuses a version change that would change what your value means. For example, if the
line bills each month on the old version and each year on the new version, a rate of `8` would
then be `8` for each year. The same applies when the metric, the price model, or the
denomination of the line changes, or when a feature changes its reset period or its metric. A
migration returns `409`. A `floating` subscription stays on its version until you change the
subscription or the plan.

### Features and grants

Access to a feature changes exactly at the start of the new version: the end of the current
period, or the move itself when the move is [immediate](#an-immediate-move). The version change
writes the entitlement changes itself, so you do not need to reconcile the subscription.

* A feature that only the new version has gets its entitlement and its grant from the start of the
  new version. Access starts at that instant.
* A feature that the new version does not have ends at the start of the new version. The last period
  before it keeps its allowance, so that period is billed against the grant it had. Access ends at
  that instant.
* If you change the version again before the start, the entitlements follow the last choice. A
  scheduled end is cleared, and a start that has not happened is removed.
* A grant for a feature that both versions have takes the allowance of the new version at its next
  reset.
* An entitlement whose `activeTo` has passed has the status `expired`.
* If you cancel an entitlement yourself after the change, Paygentic does not issue it again.

## Next steps

* [Plans](/platform/pricing/plans) — make a plan and its prices
* [Editing prices](/platform/pricing/editing-prices) — how a price edit reaches existing subscribers
* [Price intervals](/platform/billing/price-intervals) — change a price for one subscription only
* [Subscriptions](/platform/billing/subscriptions) — the subscription lifecycle


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.