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

# Updating a Subscription

> Two paths for mutating an active subscription: the changes API for deltas, and raw version lifecycle endpoints for snapshots.

Once a subscription is active, its priced item set lives inside a **version**. To change what the customer pays, you produce a new version. There are two ways to do this.

<CardGroup cols={2}>
  <Card title="Path A: Changes API" icon="bolt">
    High-level and delta-based. Describe what you want to change (add, remove, adjust) and the API computes a new version. Best for most use cases.
  </Card>

  <Card title="Path B: Version lifecycle" icon="layer-group">
    Low-level and snapshot-based. Supply a complete item set and iterate on it as a draft before publishing. Best for full control or staged multi-step edits.
  </Card>
</CardGroup>

<Note>
  Metadata-only updates (name, subscription-level discount, spending thresholds, price escalation, billing flags) use [`update-subscription`](/docs/api-reference/v2/2026-04-01/subscriptions/update-a-subscription) (`PATCH /subscriptions/{id}`) and apply immediately without creating a new version. The two paths below are specifically for changing the priced item set.
</Note>

## Preliminaries

Every version carries:

* `status`: `draft` or `published`. Only one published version is current at any moment.
* `effective_at`: when the version takes over from the previous one. Can be a specific date, or the result of a timing keyword like `immediate` or `end_of_term`.

Before mutating anything, read the current state with [`get-current-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/get-the-current-active-subscription-version).

```bash theme={null}
curl https://api.alguna.io/subscriptions/CQCncgVu/versions/current \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01"
```

The returned version `id` is the source that any change will be computed against.

## Path A: Changes API

[`create-subscription-change`](/docs/api-reference/v2/2026-04-01/subscription-changes/apply-changes-to-a-subscription) (`POST /subscriptions/{id}/changes`) is the primary endpoint for applying deltas. You supply `add`, `remove`, and `update` arrays plus an `effective` timing, and the API:

1. Reads the source version (default: the current active one, override with `source_version_id`).
2. Applies your delta on top.
3. Creates a new version with the result.
4. Publishes it, unless you pass `draft: true`.

Use [`preview-subscription-change`](/docs/api-reference/v2/2026-04-01/subscription-changes/preview-a-subscription-change) to compute the same result without writing a new version.

### Supported `effective` values

| Value                 | Meaning                                    |
| --------------------- | ------------------------------------------ |
| `immediate`           | Takes effect now. Default.                 |
| `end_of_term`         | At the end of the current contract term.   |
| `billing_cycle_start` | At the next billing cycle boundary.        |
| `end_of_contract`     | At the contract end date.                  |
| `next_renewal`        | At the next renewal date.                  |
| `YYYY-MM-DD`          | A specific date, for example `2026-07-01`. |

### A.1 Adjust a single product's scalar price field

The most common mutation. Use `update` with a partial `adjust`. Every field is optional and merged onto the existing price. Here we bump the platform fee from $500 to $600/mo effective end of term.

```bash theme={null}
curl -X POST https://api.alguna.io/subscriptions/CQCncgVu/changes \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01" \
  -H "Content-Type: application/json" \
  -d '{
    "update": [
      {
        "product_id": "prod_032wMej82trlC5RulBsDJY",
        "adjust": {
          "fixed_pricing_model": {
            "price_per_unit": "600.00"
          }
        }
      }
    ],
    "effective": "end_of_term",
    "description": "Q3 price adjustment"
  }'
```

Only `price_per_unit` was specified, so only that field is replaced. Billing interval, billing direction, tier structure, and every other field are inherited from the prior version.

### A.2 Replace a price entirely

When the new price is structurally different, for example switching from `unit` to `graduated_tiered`, use `new_price` instead of `adjust`. See the [pricing models reference](/docs/api-reference/v2/subscriptions/pricing-models) for the full list of supported types.

```json theme={null}
{
  "update": [
    {
      "product_id": "prod_04ab8Nej82trlC5RulBsDJY",
      "new_price": {
        "type": "graduated_tiered",
        "billing_interval": "monthly",
        "fee_type": "metered",
        "billing_direction": "arrears",
        "metric_ids": ["mtr_032wMej82trlC5RulBsDJY"],
        "graduated_tiered_pricing_model": {
          "tiers": [
            { "min_units": 0, "max_units": 10000, "price_per_unit": "0.00", "fixed_fee": null },
            { "min_units": 10001, "max_units": null, "price_per_unit": "0.03", "fixed_fee": null }
          ]
        }
      }
    }
  ],
  "effective": "billing_cycle_start"
}
```

### A.3 Replace tiers on a tiered price

Tiered pricing adjustments behave differently from other fields. The `tiers` array inside a tiered pricing adjustment is not merged onto the existing tiers. The array you send becomes the entire tier set.

<Warning>
  When you send a `tiers` array inside any tiered pricing adjustment (`graduated_tiered_pricing_model`, `tiered_pricing_model`, `tiered_percentage_pricing_model`, `graduated_percentage_pricing_model`, `prepaid_tiered_pricing_model`, or `prepaid_fixed_tiered_pricing_model`), the existing tier set is replaced wholesale. There is no way to add, remove, or modify a single tier in isolation. Always send the full intended tier set.

  The same replace-wholesale behavior applies to the `charges` array on `expression_pricing_model` adjustments.
</Warning>

The example below restructures the API usage product's tiers without changing the billing interval, metric bindings, or any other field on the price.

```json theme={null}
{
  "update": [
    {
      "product_id": "prod_04ab8Nej82trlC5RulBsDJY",
      "adjust": {
        "graduated_tiered_pricing_model": {
          "tiers": [
            { "min_units": 0, "max_units": 50000, "price_per_unit": "0.00", "fixed_fee": null },
            { "min_units": 50001, "max_units": 500000, "price_per_unit": "0.02", "fixed_fee": null },
            { "min_units": 500001, "max_units": null, "price_per_unit": "0.015", "fixed_fee": null }
          ]
        }
      }
    }
  ],
  "effective": "billing_cycle_start",
  "description": "New volume tiers for API usage"
}
```

If you want to keep the existing zero-priced free tier, include it in the array. If you omit it, it is gone.

### A.4 Add a new product

```json theme={null}
{
  "add": [
    {
      "product_id": "prod_07fx2Nej82trlC5RulBsDJY",
      "new_price": {
        "type": "fixed",
        "billing_interval": "monthly",
        "fee_type": "fixed",
        "billing_direction": "arrears",
        "billing_frequency": "recurring",
        "fixed_pricing_model": { "price_per_unit": "2500.00", "units": 1 }
      }
    }
  ],
  "effective": "immediate",
  "description": "Upsell: add dedicated support"
}
```

### A.5 Remove a product

```json theme={null}
{
  "remove": [
    { "product_id": "prod_0legacyNej82trlC5RulBsDJY" }
  ],
  "effective": "end_of_term"
}
```

### A.6 Swap to a different plan

Provide `plan_id` on the change request. The API swaps the item set to the plan's item set while preserving the original contract and billing config. You can combine this with `update`, `add`, and `remove` for per-customer overrides on top of the new plan.

```json theme={null}
{
  "plan_id": "pln_052xMej82trlC5RulBsDJY",
  "update": [
    {
      "product_id": "prod_032wMej82trlC5RulBsDJY",
      "adjust": {
        "fixed_pricing_model": { "price_per_unit": "800.00" }
      }
    }
  ],
  "effective": "next_renewal",
  "description": "Renewal to 2027 enterprise plan with negotiated platform fee"
}
```

### A.7 Bundle operations

Bundles use the same `add`, `remove`, and `update` shapes as standalone items, but reference `bundle_id` instead of `product_id`. Children of a bundle must be addressed through their parent bundle.

**Adjust one child price inside a bundle.** Use `update` with `bundle_id` and nested `items`.

```json theme={null}
{
  "update": [
    {
      "bundle_id": "bnd_032wMej82trlC5RulBsDJY",
      "items": [
        {
          "product_id": "prod_09tx7Nej82trlC5RulBsDJY",
          "adjust": {
            "unit_pricing_model": { "price_per_unit": "18.00" }
          }
        }
      ]
    }
  ],
  "effective": "end_of_term"
}
```

Only that one child is touched. The other children and all non-bundle items pass through unchanged.

**Add a new child to an existing bundle.** Use `add_items` on a bundle `update`.

```json theme={null}
{
  "update": [
    {
      "bundle_id": "bnd_032wMej82trlC5RulBsDJY",
      "add_items": [
        {
          "product_id": "prod_0analytNej82trlC5RulBsDJY",
          "new_price": {
            "type": "fixed",
            "billing_interval": "monthly",
            "fee_type": "fixed",
            "billing_direction": "arrears",
            "billing_frequency": "recurring",
            "fixed_pricing_model": { "price_per_unit": "100.00", "units": 1 }
          }
        }
      ]
    }
  ],
  "effective": "immediate"
}
```

**Remove a child from a bundle.** Use `remove_items`.

```json theme={null}
{
  "update": [
    {
      "bundle_id": "bnd_032wMej82trlC5RulBsDJY",
      "remove_items": [
        { "product_id": "prod_0legacyNej82trlC5RulBsDJY" }
      ]
    }
  ],
  "effective": "end_of_term"
}
```

**Remove an entire bundle.** Use top-level `remove`.

```json theme={null}
{
  "remove": [
    { "bundle_id": "bnd_032wMej82trlC5RulBsDJY" }
  ],
  "effective": "end_of_term"
}
```

### A.8 Preview a change before writing it

Every change body is also accepted by [`preview-subscription-change`](/docs/api-reference/v2/2026-04-01/subscription-changes/preview-a-subscription-change) (`POST /subscriptions/{id}/changes/preview`), which returns the computed delta without writing a new version. Use this to drive approval flows or show customers what is about to change.

```bash theme={null}
curl -X POST https://api.alguna.io/subscriptions/CQCncgVu/changes/preview \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01" \
  -H "Content-Type: application/json" \
  -d '{ "update": [ ... ], "effective": "end_of_term" }'
```

### A.9 Draft a change for later

Pass `draft: true` to produce an unpublished draft version. Useful for staging changes that need review before going live.

```json theme={null}
{
  "update": [ ],
  "effective": "2027-01-01",
  "draft": true,
  "description": "Proposed 2027 pricing"
}
```

The response includes the new version's `id` (a plain 8-char string, for example `vavGpNlc`). Publish it later via [`publish-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/publish-a-draft-subscription-version).

```bash theme={null}
curl -X POST https://api.alguna.io/subscriptions/CQCncgVu/versions/vavGpNlc/publish \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01"
```

To iterate on a draft before publishing, see Path B.

## Path B: Raw version lifecycle

When Path A's delta semantics are not enough (typically large multi-item changes, staged edits that need to be saved and reopened, or programmatic migrations), drop down to the version endpoints directly.

The lifecycle is:

1. [`create-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/create-a-new-subscription-version): `POST /subscriptions/{id}/versions`. Create a new version from a full item set. Returns a draft by default.
2. [`replace-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/replace-a-draft-subscription-version): `PUT /subscriptions/{id}/versions/{vid}`. Replace a draft's contents in place.
3. [`publish-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/publish-a-draft-subscription-version): `POST /subscriptions/{id}/versions/{vid}/publish`. Publish the draft. It becomes active at `effective_at`.
4. [`delete-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/delete-a-subscription-version): `DELETE /subscriptions/{id}/versions/{vid}`. Discard an unwanted draft.

<Warning>
  The version endpoints are snapshot-based. `POST /versions` and `PUT /versions/{vid}` require the complete item set every time. There are no deltas. Items you omit are removed from the new version.
</Warning>

### B.1 Create a draft with standalone items

```bash theme={null}
curl -X POST https://api.alguna.io/subscriptions/CQCncgVu/versions \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01" \
  -H "Content-Type: application/json" \
  -d '{
    "effective_at": "2027-01-01",
    "draft": true,
    "description": "2027 enterprise configuration",
    "items": [
      {
        "product_id": "prod_032wMej82trlC5RulBsDJY",
        "price": {
          "type": "fixed",
          "billing_interval": "monthly",
          "fee_type": "fixed",
          "billing_direction": "arrears",
          "billing_frequency": "recurring",
          "fixed_pricing_model": { "price_per_unit": "800.00", "units": 1 }
        }
      },
      {
        "product_id": "prod_04ab8Nej82trlC5RulBsDJY",
        "price": {
          "type": "graduated_tiered",
          "billing_interval": "monthly",
          "fee_type": "metered",
          "billing_direction": "arrears",
          "metric_ids": ["mtr_032wMej82trlC5RulBsDJY"],
          "graduated_tiered_pricing_model": {
            "tiers": [
              { "min_units": 0, "max_units": 100000, "price_per_unit": "0.00", "fixed_fee": null },
              { "min_units": 100001, "max_units": null, "price_per_unit": "0.02", "fixed_fee": null }
            ]
          }
        }
      }
    ]
  }'
```

The response includes the draft's `id` (for example `vavGpNlc`). The draft is not yet applied. The current active version is still in effect.

### B.2 Create a draft with bundles

Bundles go directly into `items` with `bundle_id` and a nested `items` array of priced children. Every child needs a full price definition. You can mix standalone and bundle items in the same `items` array.

```json theme={null}
{
  "effective_at": "2027-01-01",
  "draft": true,
  "items": [
    {
      "bundle_id": "bnd_032wMej82trlC5RulBsDJY",
      "items": [
        {
          "product_id": "prod_032wMej82trlC5RulBsDJY",
          "price": {
            "type": "fixed",
            "billing_interval": "monthly",
            "fee_type": "fixed",
            "billing_direction": "arrears",
            "billing_frequency": "recurring",
            "fixed_pricing_model": { "price_per_unit": "250.00", "units": 1 }
          }
        },
        {
          "product_id": "prod_09tx7Nej82trlC5RulBsDJY",
          "price": {
            "type": "unit",
            "billing_interval": "monthly",
            "fee_type": "fixed",
            "billing_direction": "arrears",
            "billing_frequency": "recurring",
            "unit_pricing_model": { "price_per_unit": "18.00" }
          }
        }
      ]
    }
  ]
}
```

### B.3 Replace a draft's contents

While a version is still a draft, edit it in place with [`replace-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/replace-a-draft-subscription-version). This is a full replacement. Send the entire item set.

```bash theme={null}
curl -X PUT https://api.alguna.io/subscriptions/CQCncgVu/versions/vavGpNlc \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01" \
  -H "Content-Type: application/json" \
  -d '{
    "effective_at": "2027-02-01",
    "description": "Pushed back to February",
    "items": [ ]
  }'
```

Replacing a version that is not in `draft` status returns `422`.

### B.4 Publish a draft

Once the draft is correct, publish it with [`publish-subscription-version`](/docs/api-reference/v2/2026-04-01/subscription-versions/publish-a-draft-subscription-version).

```bash theme={null}
curl -X POST https://api.alguna.io/subscriptions/CQCncgVu/versions/vavGpNlc/publish \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01"
```

On publish:

* The version's `status` flips to `published`.
* If `effective_at` is in the past or today, it becomes the current active version immediately.
* If `effective_at` is in the future, it sits as a pending change until then. You will see it in the parent subscription's `pending_changes` array.

### B.5 Delete an unwanted draft

```bash theme={null}
curl -X DELETE https://api.alguna.io/subscriptions/CQCncgVu/versions/vavGpNlc \
  -H "Authorization: Bearer sk_live_..." \
  -H "Alguna-Version: 2026-04-01"
```

Only `draft` versions can be deleted. Published versions are immutable historical records and must be superseded by a new version instead.

## Which path should I use?

| If you want to...                                                | Use                                                          |
| ---------------------------------------------------------------- | ------------------------------------------------------------ |
| Bump one scalar field on one price                               | Path A with `update` + `adjust`                              |
| Replace the tier structure on a tiered price                     | Path A with `update` + `adjust.<model>.tiers`                |
| Switch a price from one type to another                          | Path A with `update` + `new_price`                           |
| Add or remove a single item                                      | Path A with `add` or `remove`                                |
| Swap to a new plan with a few overrides                          | Path A with `plan_id` + `update`                             |
| Adjust one child price inside a bundle                           | Path A with `update` + `bundle_id` + nested `items[].adjust` |
| Replace a subscription's entire item set in one request          | Path B                                                       |
| Stage a big multi-step edit over several API calls               | Path B: create draft, edit with `PUT`, publish               |
| Programmatically migrate a batch of subscriptions to a new shape | Path B (snapshot-based, deterministic)                       |
| Preview a change before writing it                               | Path A with `/changes/preview`                               |

Both paths produce the same underlying data shape (a new version), so you can mix them freely across a subscription's lifetime.

## Common pitfalls

* **Editing a published version.** Published versions are immutable. `PUT` on a published version returns `422`. Create a new version via Path A or Path B instead.
* **Forgetting the complete item set in Path B.** `POST /versions` and `PUT /versions/{vid}` are snapshot endpoints. Items you do not include are removed from the new version.
* **Trying to adjust a bundle child by `product_id` alone.** Children of a bundle must be addressed through their parent: `update[].bundle_id` plus `update[].items[].product_id`, not a bare top-level `update[].product_id`.
* **Sending `adjust` and `new_price` together on the same item.** These are mutually exclusive. Use `adjust` for partial merges and `new_price` for full replacement.
* **Expecting tier merges.** The `tiers` array on any tiered pricing adjustment replaces the entire tier set. Always send the full intended tier set. The same applies to `expression_pricing_model.charges`.

For the create flow, see [Creating a Subscription](/docs/api-reference/v2/examples/creating-a-subscription).
