> ## 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.

# Price intervals

> Change the price, the quantity, or the dates of one subscription's prices without changing the plan

A price interval is one price that one subscription bills for a date range. Each
subscription has its own set of intervals. When you change an interval, you change only that
subscription. The plan, its versions, and all other subscriptions on the plan stay the same.

Use price intervals for the terms that you agree with one customer:

* a negotiated rate for one customer
* a change in seat count from a given date
* an extra price that only this customer pays, such as an onboarding fee for three months
* the end of a price, for example when a customer drops an add-on

When a customer subscribes, each price in the plan version becomes one interval. The interval
starts when the subscription starts and has no end date.

## Prerequisites

* An API key. See [API keys](/api-keys).
* A subscription on Standard Billing (`billingVersion: 1`). See
  [Billing Versions](/platform/billing/billing-versions).
* The subscription ID.

## Read the intervals

```bash theme={null}
curl https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
```

The response contains all the intervals of the subscription, in the order of their start dates.
It includes intervals that ended and intervals that start in the future. The examples on this page
use a subscription that started on 1 March 2026. It bills a monthly platform fee in advance and
metered API calls in arrears. The date today is 10 March 2026. Each example starts from these
intervals.

```json theme={null}
{
  "intervals": [
    {
      "id": "spi_p9q0r1s2t3u4v5w6",
      "priceId": "price_x7y8z9a0b1c2d3e4",
      "priceKey": "pk_m4n5o6p7q8r9s0t1",
      "kind": "plan_line",
      "planVersionId": "pver_u2v3w4x5y6z7a8b9",
      "unitPrice": null,
      "baseQuantity": "1",
      "quantityTransitions": [],
      "billingCadence": "P1M",
      "billingMode": "advance",
      "billDate": null,
      "startDate": "2026-03-01T00:00:00.000Z",
      "endDate": null
    },
    {
      "id": "spi_c3d4e5f6g7h8i9j0",
      "priceId": "price_k1l2m3n4o5p6q7r8",
      "priceKey": "pk_s9t0u1v2w3x4y5z6",
      "kind": "plan_line",
      "planVersionId": "pver_u2v3w4x5y6z7a8b9",
      "unitPrice": null,
      "baseQuantity": "1",
      "quantityTransitions": [],
      "billingCadence": "P1M",
      "billingMode": "arrears",
      "billDate": null,
      "startDate": "2026-03-01T00:00:00.000Z",
      "endDate": null
    }
  ]
}
```

| Field | Meaning |
| - | - |
| `priceKey` | The price line that the interval bills. Two intervals with the same key are two date ranges of the same line. |
| `kind` | `plan_line` if the plan version has a line with this `priceKey`. `subscription_owned` if the line exists only on this subscription. |
| `unitPrice` | `null` means that the interval bills the catalog price. A value is a rate for this subscription only. |
| `baseQuantity` | The quantity at the start of the interval. |
| `quantityTransitions` | The changes in quantity inside the interval. Each change has an `effectiveDate` and a new `quantity`. |
| `billingCadence` | The length of one billing period, as an ISO 8601 duration. `P1M` is one month. |
| `billingMode` | `advance` bills a period at its start. `arrears` bills a period at its end. |
| `startDate`, `endDate` | The interval bills from `startDate` until `endDate`. An `endDate` of `null` means that the interval has no end. |

## Send a change

All changes go to one endpoint, `POST /v0/subscriptions/{id}/intervals`. The body has three
lists. Each list is optional, but you must send one operation or more.

* `add` makes new intervals.
* `edit` changes intervals, by `id`.
* `remove` deletes intervals, by `id`.

The API applies all the operations of one request together. If one operation is not valid, the API
applies none of them. Each list accepts 25 operations or fewer.

An `edit` can change four fields: `unitPrice`, `baseQuantity`, `quantityTransitions`, and `endDate`.
If you do not send a field, it stays the same. You can send the other fields too, but each one must
be equal to its current value. So you can send an interval from the `GET` response back as an
`edit`. Delete its `kind` field first, because an `edit` does not accept it. If nothing changes,
the response has `"unchanged": true`.

If a request times out, do not send an `add` again. If the first request succeeded, the second
`add` overlaps the interval that the first one made, and the API refuses it with a 409. Read the
intervals to see the result.

After a change, the API calculates again the invoice lines that are not on an invoice yet. It does
not change an invoice that exists. See [The response](#the-response).

## The billing periods of a price

Each date that you send must agree with the billing periods of the price. The periods repeat at
the `billingCadence` of the price, from the billing anchor of the subscription:

* For most subscriptions, the billing anchor is the start of the subscription.
* If the billing anchor is after the start, the time from the start to the anchor is a partial
  first period. The full periods repeat from the anchor.

The example subscription has its billing anchor at its start, 1 March. So its monthly periods start
on 1 March, 1 April, 1 May, and so on.

The API applies these rules to each `startDate` and `endDate` that you send:

* **A date on a period boundary.** The API accepts it. Each period bills in full.
* **A date inside a period.** The API accepts it and splits the period at that date. Each part
  bills for the part of the period that it covers, measured in time. For example, a monthly fee
  that ends on 16 April bills 15 days of the 30 days of April.
* **A date before the billing anchor.** The API refuses it. The start of the subscription is
  the only exception.

Some prices cannot bill a part of a period. For these prices, send dates on a period boundary
only:

* a metered price whose billable metric does not use `sum` or `count` aggregation
* a metered price with grant discounts (`grantDiscountEnabled`)
* a price with the `volume` model or the legacy `percentage` model
* a price that is owed in full for each period (`isObligation: true`)

The API does not check the dates of the intervals that already exist. An existing interval that
does not agree with the periods does not stop you from changing the subscription.

## Change a price

To give one subscription a different rate, set `unitPrice` on its interval. Write the rate as a
decimal string, per unit and per period. You can set `unitPrice` only on a price with the
`standard` model.

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edit": [
      { "id": "spi_c3d4e5f6g7h8i9j0", "unitPrice": "0.0015" }
    ]
  }'
```

This edit changes the rate for all periods of the interval, so it is correct only if no period of
the interval is invoiced. In the example, the API calls bill in arrears, and the March period is not
invoiced until 1 April. So the edit applies from 1 March.

To go back to the catalog price, set `unitPrice` to `null`.

### Change a price from a date

The platform fee bills in advance, so the March period is invoiced on 1 March. An edit to the rate
of the full interval changes March too, and the API refuses it. To change the rate from 1 April,
send these two operations in one request:

1. Set the `endDate` of the interval to 1 April.
2. Add an interval for the same `priceKey` that starts on 1 April.

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edit": [
      { "id": "spi_p9q0r1s2t3u4v5w6", "endDate": "2026-04-01T00:00:00Z" }
    ],
    "add": [
      {
        "priceKey": "pk_m4n5o6p7q8r9s0t1",
        "unitPrice": "90.00",
        "baseQuantity": "1",
        "billingCadence": "P1M",
        "billingMode": "advance",
        "startDate": "2026-04-01T00:00:00Z"
      }
    ]
  }'
```

The new interval must start at the same instant that the old interval ends. If there is time
between the two intervals, or if they overlap, the API refuses the request. For a `priceKey` that
is on the plan version, the API takes the price from the plan version, so you do not send
`priceId`.

## Change a quantity

To change the quantity for the full interval, set `baseQuantity`. To change it from a date, add
a transition:

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edit": [
      {
        "id": "spi_p9q0r1s2t3u4v5w6",
        "quantityTransitions": [
          { "effectiveDate": "2026-04-01T00:00:00Z", "quantity": "5" }
        ]
      }
    ]
  }'
```

The platform fee bills 1 unit for March and 5 units from April. `quantityTransitions` replaces
the full list, so send all the transitions that you want to keep. The same rule about invoiced
periods applies. In the example, a `baseQuantity` of `5` changes March, so the API refuses it.

## Add a price to one subscription

To bill a price that is not on the plan, add an interval with a new `priceKey` and the `priceId` of
a catalog price. The interval is `subscription_owned`: it belongs to this subscription only.

<Warning>
  Check the `kind` of the new interval in the response. If you mistype the `priceKey` of a plan
  line and send a `priceId`, the API makes a `subscription_owned` interval. The plan line does not
  change.
</Warning>

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "add": [
      {
        "priceKey": "onboarding-support",
        "priceId": "price_a1b2c3d4e5f6g7h8",
        "unitPrice": "50.00",
        "baseQuantity": "1",
        "billingCadence": "P1M",
        "billingMode": "advance",
        "startDate": "2026-04-01T00:00:00Z",
        "endDate": "2026-07-01T00:00:00Z"
      }
    ]
  }'
```

This interval bills \$50.00 for April, May, and June. For a price with no end, do not send
`endDate`, or send `null`.

Obey these rules for a new `priceKey`:

* Start the key with a lowercase letter or a digit. Use only lowercase letters, digits, hyphens,
  and underscores. Use 64 characters or fewer.
* Send `unitPrice` in all cases. If you send `null`, the interval keeps the catalog price that
  applies on the day that you add it. Later changes to the catalog price do not change it.
* Use a catalog price that no other interval of the subscription bills at the same time. Two
  intervals with the same `priceId` must not overlap.
* Use a metered price with `billingMode: "arrears"` only.

If the subscription moves to a different plan version, a `subscription_owned` interval with no
`endDate` stops at that change. An interval with an `endDate` keeps its dates.

## Close a price

To stop a price, set the `endDate` of its interval:

```bash theme={null}
curl -X POST https://api.paygentic.io/v0/subscriptions/sub_z9a0b1c2d3e4f5g6/intervals \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "edit": [
      { "id": "spi_c3d4e5f6g7h8i9j0", "endDate": "2026-06-01T00:00:00Z" }
    ]
  }'
```

The API calls bill until 1 June. The periods from 1 June bill nothing for this price. The periods
before 1 June do not change. To close a price at the end of a period, send the boundary date. A
date inside a period splits that period, as described in
[The billing periods of a price](#the-billing-periods-of-a-price).

To open a closed interval again, set `endDate` to `null`. You can do this only while no period
after the old `endDate` is invoiced.

To delete an interval, put its `id` in `remove`. Do this only for an interval that has no invoiced
period, such as an interval that starts in the future. The API refuses a request that removes all
the intervals of a subscription.

## What the API refuses

### An invoiced period cannot change

A period is invoiced when its invoice moment is now or in the past:

* A price that bills in `advance` has its invoice moment at the start of the period.
* A price that bills in `arrears` has its invoice moment at the end of the period.
* If a date splits a period, each part has its own invoice moment.

The API refuses each request that changes an invoiced period. This includes a change to its rate,
its quantity, or its dates, and a change that removes the interval that covers it. The customer
has an invoice for that period, and the request cannot change that invoice. So, on 10 March, you
can change the March rate of a price that bills in arrears, but not of a price that bills in
advance.

If the subscription uses a test clock, the API uses the time of the test clock.

```json theme={null}
{
  "message": "A committed billing period would change",
  "error": "bad_request",
  "code": "INTERVAL_PERIOD_ALREADY_BILLED",
  "details": {
    "priceKey": "pk_m4n5o6p7q8r9s0t1",
    "periodStart": "2026-03-01T00:00:00.000Z",
    "invoiceAt": "2026-03-01T00:00:00.000Z",
    "change": "repriced"
  }
}
```

`details.periodStart` is the first invoiced period of the price. `details.change` tells you what
the request changed: `repriced`, `requantified`, `reshaped`, or `uncovered`. To fix the request,
start the change at the end of the last invoiced period, or later.

### Other refusals

| Status | Response | Cause | What to do |
| - | - | - | - |
| 409 | `An interior gap in price coverage would be created` | Two intervals of the same `priceKey` have time between them. `details` gives `gapStart` and `gapEnd`. | Start the second interval when the first ends. For a date range with no charge, add an interval with a `unitPrice` of `"0"`. |
| 409 | `Two intervals for the same price would overlap` | Two intervals with the same `priceKey` or the same `priceId` cover the same time. | Close the first interval when the second starts, in the same request. |
| 409 | `This write would leave the subscription with no price` | The request removes all the intervals. | Keep one interval or more. To end the subscription, terminate it. |
| 409 | `An add's planVersionId must match the subscription's own pinned version` | The `planVersionId` of an `add` is not the plan version of the subscription. | Send the `planVersionId` from the `GET` response, or do not send it. |
| 400 | `Interval field '<field>' is not editable and does not match` | An `edit` changes a field other than the four fields that you can edit. | To change the dates or the price of an interval, close it and add a new interval. |
| 400 | code `INTERVAL_START_NOT_PERIOD_BOUNDARY` | A date is before the billing anchor, and is not the start of the subscription. | Use the start of the subscription, or a date on or after the billing anchor. |
| 400 | code `INTERVAL_PRICE_NOT_SPLITTABLE` or `INTERVAL_AGGREGATION_NOT_SPLITTABLE` | A date is inside a period of a price that cannot bill a part of a period. | Use a period boundary. |
| 400 | code `INTERVAL_END_NOT_AFTER_START` | The `endDate` is not after the `startDate`. | Send a later `endDate`. |
| 400 | code `INTERVAL_METERED_ADVANCE_NOT_BILLABLE` | An `add` bills a metered price in `advance`. | Use `billingMode: "arrears"`. |
| 400 | code `INTERVAL_ADD_ONE_OFF_NOT_SUPPORTED` | An `add` has `billingCadence: "one_off"`. | Use a recurring cadence. |
| 400 | code `INTERVAL_ONE_OFF_PRICE_BILLS_MORE_THAN_ONCE` | A request makes a catalog price that bills one time bill in two intervals. | Bill the price in one interval only. |
| 400 | code `OVERRIDE_MODEL_NOT_OVERRIDABLE` | A `unitPrice` is set on a price whose model is not `standard`. | Send `unitPrice: null`, or use a `standard` price. |
| 400 | `An add whose key names no price slot on this subscription’s plan version must carry a priceId` | The `priceKey` is not on the plan version, and the `add` has no `priceId`. | Correct the `priceKey`, or send a `priceId`. |
| 400 | `key "<priceKey>" names a price slot on this subscription’s plan version, which supplies the price; priceId must match it or be omitted` | The `priceKey` is on the plan version, and the `priceId` is a different price. | Do not send `priceId` for a plan line. |
| 429 | `This subscription’s price timeline is being edited by another request; please retry.` | A different request changes the same subscription at the same time. | Send the request again after the number of seconds in the `Retry-After` header. |
| 429 | `This subscription’s plan catalog is being edited by another request; please retry.` | A different request changes the plan or its prices at the same time. | Send the request again after the number of seconds in the `Retry-After` header. |

## The response

A successful request returns all the intervals after the change in `intervals`, and two more
fields:

```json theme={null}
{
  "unchanged": false,
  "lineItems": { "created": 1, "removed": 1 }
}
```

* `unchanged` is `true` if the request changed nothing.
* `lineItems` counts the prices whose invoice lines the API calculated again (`created`) or
  deleted (`removed`).
* `lineItems.syncFailed` is `true` if the API saves the change but cannot calculate the invoice
  lines again. The change stays saved, and the API tries again automatically. To try again
  immediately, send the same request again.

## Next steps

* [Get Price Intervals](/api-reference/subscriptions/get-price-intervals) and
  [Edit Price Intervals](/api-reference/subscriptions/edit-price-intervals) — the API reference
* [Editing prices](/platform/pricing/editing-prices) — change a price for all subscribers
* [Quantity bounds](/platform/billing/quantity-bounds) — bill a contracted minimum or maximum quantity
* [Subscriptions](/platform/billing/subscriptions) — the subscription lifecycle
