# Preview a plan or quantity change

> The portal.subscriptions.preview_change operation: POST /api/v1/portal/me/subscriptions/{subscriptionId}/preview_change in the REST API and the portal_subscriptions_preview_change tool in the MCP server.

Shows exactly what switching one item of the signed-in customer's subscription to another plan, or changing its
quantity, would do, without changing anything: an upgrade applies now and its invoice is issued and charged now
(`invoice`: the credit for the unused time, the charge for the time left, tax and the amount due); a downgrade
applies at the end of the period with nothing to pay now. The brand's portal settings decide what may change:
a plan switch only to a plan its plan-change matrix lists for the item's plan, a quantity change only when the
brand allows it. Confirm with `portal.subscriptions.update` and the preview's `version`.

Proration is exact to the second, so the real change may differ by the time that passed. Customer portal only
(`portal:read`).

## At a glance

- **REST:** `POST /api/v1/portal/me/subscriptions/{subscriptionId}/preview_change`
- **MCP tool:** `portal_subscriptions_preview_change`
- **Operation ID:** `portal.subscriptions.preview_change`
- **Scope:** Brand
- **Permission:** `portal:read`
- **Risk tier:** `read`
- **Dry run:** Not supported
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** A customer signed in to the customer portal (API keys can't call it)
- **Since:** M9

## Request

### Path parameters

| Field            | Type       | Description                          |
| ---------------- | ---------- | ------------------------------------ |
| `subscriptionId` | `sub_…` ID | One of your subscriptions (`sub_…`). |

### Body

A JSON object (`Content-Type: application/json`):

| Field      | Type                   | Required | Description                                                                              |
| ---------- | ---------------------- | -------- | ---------------------------------------------------------------------------------------- |
| `itemId`   | `si_…` ID              | Yes      | The subscription item to change (`si_…`).                                                |
| `priceId`  | `price_…` ID           | No       | Switch the item to this price: one the brand's plan-change matrix allows from its price. |
| `quantity` | integer (1 to 1000000) | No       | The item's new quantity (seats), when the brand lets customers change it.                |

## Response

`200 OK` with a JSON object:

| Field                            | Type                                                                              | Description                                                                                                                                                                                                        |
| -------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `object`                         | `"portal_subscription_change_preview"`                                            |                                                                                                                                                                                                                    |
| `subscriptionId`                 | `sub_…` ID                                                                        |                                                                                                                                                                                                                    |
| `direction`                      | one of `upgrade`, `downgrade`, `lateral`                                          |                                                                                                                                                                                                                    |
| `effective`                      | one of `now`, `period_end`                                                        | Upgrades apply now; downgrades at the period end.                                                                                                                                                                  |
| `effectiveAt`                    | date-time                                                                         |                                                                                                                                                                                                                    |
| `settlement`                     | one of `invoice_now`, `pending_items`, `credit_balance`, `pending_update`, `none` | `invoice_now`: invoiced and charged now (`invoice`); `pending_items`: added to your next invoice; `credit_balance`: credited to your balance; `pending_update`: applies at `effectiveAt`; `none`: nothing to bill. |
| `invoice`                        | object or null                                                                    |                                                                                                                                                                                                                    |
| `pendingItems`                   | array of object                                                                   |                                                                                                                                                                                                                    |
| `pendingItems[].description`     | string                                                                            |                                                                                                                                                                                                                    |
| `pendingItems[].amount`          | money                                                                             |                                                                                                                                                                                                                    |
| `pendingItems[].periodStart`     | date-time or null                                                                 |                                                                                                                                                                                                                    |
| `pendingItems[].periodEnd`       | date-time or null                                                                 |                                                                                                                                                                                                                    |
| `credit`                         | money or null                                                                     |                                                                                                                                                                                                                    |
| `items`                          | array of object                                                                   | The subscription's items after the change.                                                                                                                                                                         |
| `items[].priceId`                | `price_…` ID                                                                      |                                                                                                                                                                                                                    |
| `items[].productId`              | `prod_…` ID                                                                       |                                                                                                                                                                                                                    |
| `items[].productName`            | string                                                                            |                                                                                                                                                                                                                    |
| `items[].priceName`              | string or null                                                                    | The price's nickname, e.g. “Pro, monthly”.                                                                                                                                                                         |
| `items[].interval`               | object                                                                            |                                                                                                                                                                                                                    |
| `items[].interval.interval`      | one of `day`, `week`, `month`, `year`                                             |                                                                                                                                                                                                                    |
| `items[].interval.intervalCount` | integer                                                                           |                                                                                                                                                                                                                    |
| `items[].usageType`              | one of `licensed`, `metered`                                                      |                                                                                                                                                                                                                    |
| `items[].unitAmount`             | money or null                                                                     | A per-unit price's amount per unit and period (null for tiered and metered prices).                                                                                                                                |
| `items[].taxBehavior`            | one of `inclusive`, `exclusive`                                                   | `exclusive`: tax is added on the invoice.                                                                                                                                                                          |
| `items[].id`                     | `si_…` ID                                                                         |                                                                                                                                                                                                                    |
| `items[].quantity`               | integer                                                                           |                                                                                                                                                                                                                    |
| `items[].amount`                 | money or null                                                                     | The item's full-period amount at its quantity, before discounts (null for metered items).                                                                                                                          |
| `periodAmount`                   | money                                                                             | The licensed items' full-period amount after the change, before tax.                                                                                                                                               |
| `version`                        | integer                                                                           | Confirm with this as `expectedVersion`.                                                                                                                                                                            |

## Examples

### Upgrading from Plus to Pro mid-period

```http
POST /api/v1/portal/me/subscriptions/sub_01m4tx5nj8emwbyf7y9vcf48c0/preview_change
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "itemId": "si_01m4tx5nj8emwbyf7y9vcf48c1",
  "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3"
}
```

Response (`200`):

```json
{
  "object": "portal_subscription_change_preview",
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "direction": "upgrade",
  "effective": "now",
  "effectiveAt": "2026-11-05T09:30:00.000Z",
  "settlement": "invoice_now",
  "invoice": {
    "currency": "EUR",
    "lines": [
      {
        "description": "Unused time on Genno Plus (Plus, monthly)",
        "quantity": 1,
        "amount": {
          "amount": "-4500000",
          "currency": "EUR"
        },
        "proration": true,
        "periodStart": "2026-11-05T09:30:00.000Z",
        "periodEnd": "2026-11-20T10:00:00.000Z"
      },
      {
        "description": "Remaining time on Genno Pro (Pro, monthly)",
        "quantity": 1,
        "amount": {
          "amount": "14500000",
          "currency": "EUR"
        },
        "proration": true,
        "periodStart": "2026-11-05T09:30:00.000Z",
        "periodEnd": "2026-11-20T10:00:00.000Z"
      }
    ],
    "subtotal": {
      "amount": "8196721",
      "currency": "EUR"
    },
    "tax": {
      "amount": "1803279",
      "currency": "EUR"
    },
    "total": {
      "amount": "10000000",
      "currency": "EUR"
    },
    "creditApplied": null,
    "amountDue": {
      "amount": "10000000",
      "currency": "EUR"
    }
  },
  "pendingItems": [],
  "credit": null,
  "items": [
    {
      "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c3",
      "productId": "prod_01m4tx5nj8emwbyf7y9vcf48c5",
      "productName": "Genno Pro",
      "priceName": "Pro, monthly",
      "interval": {
        "interval": "month",
        "intervalCount": 1
      },
      "usageType": "licensed",
      "unitAmount": {
        "amount": "29000000",
        "currency": "EUR"
      },
      "taxBehavior": "inclusive",
      "id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
      "quantity": 1,
      "amount": {
        "amount": "29000000",
        "currency": "EUR"
      }
    }
  ],
  "periodAmount": {
    "amount": "29000000",
    "currency": "EUR"
  },
  "version": 4
}
```

## Errors

| Code                                                                                                       | HTTP status | When                                                                                                                                                                                                                                        | What to do                                                                                                                                                                                                                                                     |
| ---------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`validation_failed`](https://ownrecord.io/docs/reference/errors.md#validation_failed)                     | 400         | A field is malformed, or neither `priceId` nor `quantity` was sent.                                                                                                                                                                         | Fix the fields listed in `details.issues` and send the request again.                                                                                                                                                                                          |
| [`resource_not_found`](https://ownrecord.io/docs/reference/errors.md#resource_not_found)                   | 404         | You have no such subscription (another customer's reads the same).                                                                                                                                                                          | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                                                           |
| [`subscription_item_not_found`](https://ownrecord.io/docs/reference/errors.md#subscription_item_not_found) | 404         | `itemId` isn't one of the subscription's items.                                                                                                                                                                                             | Read the subscription and use one of its `items[].id` (`si_…`); to add a new price, leave out `id`.                                                                                                                                                            |
| [`portal_action_not_allowed`](https://ownrecord.io/docs/reference/errors.md#portal_action_not_allowed)     | 403         | The brand's portal settings or the subscription's state don't allow this change: plan switches need the brand's plan-change matrix to list the target, quantity changes need the brand's permission and a licensed item (`details.action`). | The brand's portal settings don't allow it for this subscription now (`details.action`): contact the brand.                                                                                                                                                    |
| [`subscription_change_invalid`](https://ownrecord.io/docs/reference/errors.md#subscription_change_invalid) | 400         | The change can't be made now (for example a change already waits for its invoice to be paid).                                                                                                                                               | Check `details.reason` and adjust the request.                                                                                                                                                                                                                 |
| [`subscription_status_invalid`](https://ownrecord.io/docs/reference/errors.md#subscription_status_invalid) | 409         | The subscription can't change in its status.                                                                                                                                                                                                | Check `details.status`: items change only while trialing, active or past due; pausing needs an active subscription and resuming a paused one; reactivating needs a cancellation scheduled for the period end; canceled and expired subscriptions don't change. |
| [`subscription_price_invalid`](https://ownrecord.io/docs/reference/errors.md#subscription_price_invalid)   | 400         | The new plan's price is archived.                                                                                                                                                                                                           | Use active recurring prices of one currency whose intervals are whole multiples of each other (monthly with yearly, weekly with 2-weekly). `details.reason` says what's wrong and `param` names the item.                                                      |
| [`jurisdiction_blocked`](https://ownrecord.io/docs/reference/errors.md#jurisdiction_blocked)               | 422         | The brand can't sell in your country now (an invoice due now can't be issued).                                                                                                                                                              | This jurisdiction isn't covered by usable tax knowledge for this seller. Contact support if you need to sell there.                                                                                                                                            |
| [`registration_required`](https://ownrecord.io/docs/reference/errors.md#registration_required)             | 422         | The brand can't invoice your country yet (an invoice due now can't be issued).                                                                                                                                                              | Register the entity for the regime named in `details.regime` (see its prerequisites and deadline), or arm the jurisdiction once the prerequisites are done. The customer can join the waitlist meanwhile.                                                      |
| [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed)               | 403         | The portal session is in the other mode than the request.                                                                                                                                                                                   | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.                                                                                                                                    |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                     | 403         | The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).                                                                                                           | Ask an organization owner or admin for a role or scope that allows this action.                                                                                                                                                                                |

It can also return these [common errors](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`dry_run_not_supported`](https://ownrecord.io/docs/reference/errors.md#dry_run_not_supported), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
