Skip to content

Preview a subscription change

The subscriptions.preview_change operation: POST /api/v1/subscriptions/{id}/preview_change in the REST API and the subscriptions_preview_change tool in the MCP server.

POST/api/v1/subscriptions/{id}/preview_change

Shows exactly what subscriptions.update would do with the same item changes, proration behaviour, timing and promotion code, without changing anything: whether it is an upgrade, a downgrade or lateral, when it applies, and how it is settled (settlement): the invoice issued now (invoice: lines, prorations, discounts, tax at today's rates, the credit balance applied, the amount due; without a number until it is issued), the pending items for the next invoice, the credit to the customer's credit balance, or the scheduled change. Also the subscription as it would be and its monthly recurring amount before and after.

The preview runs the change's own code in a transaction that is rolled back, so it matches the real change as long as nothing changes in between (the time moves on: proration is exact to the second).

At a glance

  • REST: POST /api/v1/subscriptions/{id}/preview_change
  • MCP tool: subscriptions_preview_change
  • Operation ID: subscriptions.preview_change
  • Scope: Brand
  • Permission: subscriptions:read
  • Risk tier: read
  • Dry run: Not supported
  • Idempotency: Idempotency-Key required
  • Modes: Test and live
  • Authentication: API key with the subscriptions:read scope
  • Since: M5

Request

Path parameters

FieldTypeDescription
idsub_… ID

Body

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

FieldTypeRequiredDescription
itemsarray of objectNoItem changes: { id, priceId } swaps a price, { id, quantity } changes seats, { priceId, quantity } adds an item, { id, deleted: true } removes one. Items not named stay.
items[].idsi_… IDNoThe item to change or remove (si_…); leave out to add one.
items[].priceIdprice_… IDNoThe new price of the item, or of a new item.
items[].quantityinteger (1 to 1000000)NoSeats or units of a licensed price (1 to 1,000,000). Metered prices take no quantity.
items[].deletedbooleanNoRemove the item (with id).
prorationBehaviorone of create_prorations, always_invoice, noneNocreate_prorations (credit unused time and charge the remaining time on the next invoice; a net credit goes to the customer credit balance), always_invoice (invoice the proration now) or none.
effectiveone of now, period_endNonow, or at the end of the current period (period_end, stored as pendingUpdate). Default: upgrades and interval changes now with always_invoice, downgrades at the period end.
paymentBehaviorone of allow_incomplete, pending_if_incompleteNoFor a change invoiced now: allow_incomplete (default; the change applies and a failed payment goes to dunning) or pending_if_incomplete (the old items stay until the invoice is paid).
couponIdcpn_… IDNoApply a coupon to the subscription.
promotionCodestringNoA promotion code as the customer typed it (case doesn't matter).

Response

200 OK with a JSON object:

FieldTypeDescription
object"subscription_change_preview"
subscriptionIdsub_… ID
directionone of upgrade, downgrade, lateral
intervalChangeboolean
effectiveone of now, period_end
prorationBehaviorone of create_prorations, always_invoice, none
settlementone of invoice_now, pending_items, credit_balance, pending_update, noneinvoice_now: invoice is issued now; pending_items: the prorations wait for the next invoice; credit_balance: a net credit goes to the customer credit balance; pending_update: the change waits (period end or payment); none: nothing to bill.
invoiceobject or nullThe exact invoice the change issues now (its number is taken when it is issued).
pendingItemsarray of object
pendingItems[].descriptionstring
pendingItems[].amountmoney
pendingItems[].periodStartdate-time or null
pendingItems[].periodEnddate-time or null
creditmoney or nullWhat goes to the customer credit balance.
subscriptionobjectThe subscription as it would be after the change.
subscription.idsub_… ID
subscription.object"subscription"
subscription.livemodeboolean
subscription.entityIdent_… ID
subscription.brandIdbrd_… ID
subscription.customerIdcus_… ID
subscription.statusone of incomplete, incomplete_expired, trialing, active, past_due, unpaid, paused, canceledincomplete (waiting for the first payment, 23 hours), trialing, active, past_due, unpaid, paused, canceled or incomplete_expired (spec §16.1).
subscription.itemsarray of object
subscription.items[].idsi_… ID
subscription.items[].priceIdprice_… ID
subscription.items[].productIdprod_… ID
subscription.items[].quantityinteger
subscription.items[].createdAtdate-time
subscription.currencystring
subscription.mrrmoney or nullThe items' monthly recurring amount (licensed items, before discounts and tax).
subscription.collectionMethodone of charge_automatically, send_invoice
subscription.daysUntilDueinteger or null
subscription.defaultPaymentMethodIdpm_… ID or null
subscription.billingCycleAnchordate-time
subscription.currentPeriodStartdate-time
subscription.currentPeriodEnddate-time
subscription.trialStartdate-time or null
subscription.trialEnddate-time or null
subscription.trialEndBehaviorone of convert, cancel_if_no_payment_method, pause_if_no_payment_method
subscription.cancelAtPeriodEndboolean
subscription.cancelAtdate-time or null
subscription.canceledAtdate-time or null
subscription.endedAtdate-time or null
subscription.cancellationobject or null
subscription.pauseobject or null
subscription.discountIdsarray of disc_… ID
subscription.pendingUpdateobject or null
subscription.latestInvoiceIdinv_… ID or null
subscription.incompleteExpiresAtdate-time or null
subscription.testClockIdclk_… ID or null
subscription.dunningobject or nullDunning (§16.10) while past due or unpaid; null otherwise.
subscription.usageThresholdmoney or nullUnbilled metered usage worth this much is invoiced at once (spec §16.7).
subscription.entitlementGraceUntildate-time or null
subscription.metadataobject
subscription.versioninteger
subscription.createdAtdate-time
subscription.updatedAtdate-time
mrrBeforemoney
mrrAftermoney
mrrDeltamoney

Examples

What upgrading to Pro now costs

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

{
  "items": [
    {
      "id": "si_01m4tx5nj8emwbyf7y9vcf47za",
      "priceId": "price_01m4tx5nj8emwbyf7y9vcf47y3"
    }
  ]
}

Response (200):

JSON
{
  "object": "subscription_change_preview",
  "subscriptionId": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
  "direction": "upgrade",
  "intervalChange": false,
  "effective": "now",
  "prorationBehavior": "always_invoice",
  "settlement": "invoice_now",
  "invoice": {
    "id": "inv_01m4tx5nj8emwbyf7y9vcf47yk",
    "object": "invoice",
    "livemode": false,
    "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
    "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
    "number": null,
    "numbering": null,
    "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
    "subscriptionId": null,
    "collectionMethod": "send_invoice",
    "kind": "invoice",
    "status": "open",
    "language": "en",
    "issuedAt": "2026-10-20T09:00:00.000Z",
    "issueDate": "2026-10-20",
    "taxPoint": "2026-10-20T09:00:00.000Z",
    "taxPointDate": "2026-10-20",
    "daysUntilDue": 14,
    "dueDate": "2026-11-03",
    "seller": {
      "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
      "legalName": "Acme Digital d.o.o.",
      "tradingName": null,
      "address": {
        "line1": "Ilica 1",
        "line2": null,
        "city": "Zagreb",
        "postalCode": "10000",
        "region": null,
        "country": "HR"
      },
      "companyRegistryId": "080000001",
      "taxIds": [
        {
          "type": "eu_vat",
          "value": "HR12345678901",
          "regimeId": null
        }
      ],
      "brand": {
        "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
        "name": "Genno",
        "primaryColor": "#0E6B5C",
        "accentColor": null,
        "supportEmail": "help@genno.example",
        "supportUrl": null
      },
      "footerText": null
    },
    "buyer": {
      "type": "b2b",
      "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47ym",
      "name": "Alpen Apps GmbH",
      "email": "billing@alpen.example",
      "address": null,
      "country": "AT",
      "taxId": {
        "type": "eu_vat",
        "value": "ATU00000000",
        "country": "AT"
      }
    },
    "currency": "EUR",
    "lines": [
      {
        "id": "1",
        "description": "Implementation workshop (days)",
        "productId": "prod_01m4tx5nj8emwbyf7y9vcf47yr",
        "priceId": null,
        "quantity": 2,
        "taxCategoryId": "consulting_remote",
        "taxBehavior": "exclusive",
        "unitAmount": {
          "amount": "600000000",
          "currency": "EUR"
        },
        "net": {
          "amount": "1200000000",
          "currency": "EUR"
        },
        "tax": {
          "ratePpm": "0",
          "amount": {
            "amount": "0",
            "currency": "EUR"
          },
          "jurisdictionId": "AT",
          "regimeId": "EU_OSS_UNION",
          "decision": "reverse_charge",
          "rateKind": null,
          "rateId": null,
          "pendingRegistration": false
        },
        "gross": {
          "amount": "1200000000",
          "currency": "EUR"
        },
        "kind": "invoice_item",
        "period": null,
        "subscriptionItemId": null,
        "invoiceItemId": "ii_01m4tx5nj8emwbyf7y9vcf47ys",
        "discount": null,
        "meteredQuantity": null
      }
    ],
    "totals": {
      "net": {
        "amount": "1200000000",
        "currency": "EUR"
      },
      "tax": {
        "amount": "0",
        "currency": "EUR"
      },
      "gross": {
        "amount": "1200000000",
        "currency": "EUR"
      },
      "taxByRate": [
        {
          "ratePpm": "0",
          "jurisdictionId": "AT",
          "regimeId": "EU_OSS_UNION",
          "decision": "reverse_charge",
          "net": {
            "amount": "1200000000",
            "currency": "EUR"
          },
          "tax": {
            "amount": "0",
            "currency": "EUR"
          }
        }
      ]
    },
    "taxCurrencyTotals": null,
    "creditApplied": {
      "amount": "200000000",
      "currency": "EUR"
    },
    "amountDue": {
      "amount": "1000000000",
      "currency": "EUR"
    },
    "notes": [
      {
        "code": "reverse_charge",
        "text": "Prijenos porezne obveze (reverse charge) – čl. 17. st. 1. Zakona o PDV-u"
      }
    ],
    "fieldCheck": {
      "invoicingRuleIds": [],
      "missing": ["buyer_address"],
      "unchecked": []
    },
    "pendingRegistration": false,
    "paymentIds": [],
    "creditNoteIds": [],
    "ledgerTxnIds": ["txn_01m4tx5nj8emwbyf7y9vcf47yn"],
    "quoteId": "tq_01m4tx5nj8emwbyf7y9vcf47xr",
    "quoteIds": ["tq_01m4tx5nj8emwbyf7y9vcf47xr"],
    "pdf": {
      "status": "pending",
      "fileId": null,
      "sha256": null,
      "size": null,
      "templateVersion": null,
      "renderedAt": null,
      "url": "/api/v1/invoices/inv_01m4tx5nj8emwbyf7y9vcf47yk/pdf"
    },
    "finalizedAt": "2026-10-20T09:00:00.000Z",
    "paidAt": null,
    "payment": null,
    "voidedAt": null,
    "markedUncollectibleAt": null,
    "emailedAt": null,
    "sentAt": null,
    "draft": null,
    "metadata": {
      "contract": "ENT-2026-07"
    },
    "version": 2,
    "createdAt": "2026-10-20T08:30:00.000Z",
    "updatedAt": "2026-10-20T09:00:00.000Z"
  },
  "pendingItems": [],
  "credit": null,
  "subscription": {
    "id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
    "object": "subscription",
    "livemode": false,
    "entityId": "ent_01m4tx5nj8emwbyf7y9vcf47zm",
    "brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
    "customerId": "cus_01m4tx5nj8emwbyf7y9vcf47z4",
    "status": "active",
    "items": [
      {
        "id": "si_01m4tx5nj8emwbyf7y9vcf47za",
        "priceId": "price_01m4tx5nj8emwbyf7y9vcf47y3",
        "productId": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
        "quantity": 3,
        "createdAt": "2026-10-13T12:00:00.000Z"
      }
    ],
    "currency": "EUR",
    "mrr": {
      "amount": "147000000",
      "currency": "EUR"
    },
    "collectionMethod": "charge_automatically",
    "daysUntilDue": null,
    "defaultPaymentMethodId": "pm_01m4tx5nj8emwbyf7y9vcf47zd",
    "billingCycleAnchor": "2026-10-13T12:00:00.000Z",
    "currentPeriodStart": "2026-10-13T12:00:00.000Z",
    "currentPeriodEnd": "2026-11-13T12:00:00.000Z",
    "trialStart": null,
    "trialEnd": null,
    "trialEndBehavior": "convert",
    "cancelAtPeriodEnd": false,
    "cancelAt": null,
    "canceledAt": null,
    "endedAt": null,
    "cancellation": null,
    "pause": null,
    "discountIds": [],
    "pendingUpdate": null,
    "latestInvoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
    "incompleteExpiresAt": null,
    "testClockId": null,
    "dunning": null,
    "usageThreshold": null,
    "entitlementGraceUntil": null,
    "metadata": {
      "crm_account": "A-1042"
    },
    "version": 2,
    "createdAt": "2026-10-13T12:00:00.000Z",
    "updatedAt": "2026-10-13T12:00:00.000Z"
  },
  "mrrBefore": {
    "amount": "87000000",
    "currency": "EUR"
  },
  "mrrAfter": {
    "amount": "147000000",
    "currency": "EUR"
  },
  "mrrDelta": {
    "amount": "60000000",
    "currency": "EUR"
  }
}

Errors

CodeHTTP statusWhenWhat to do
validation_failed400A field is malformed or out of range.Fix the fields listed in details.issues and send the request again.
resource_not_found404No subscription (or customer, price, coupon) with this ID is visible to the request.Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.
subscription_status_invalid409The subscription's status doesn't allow this (spec §16.1): see details.reason.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_item_not_found404An item id 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.
subscription_price_invalid400A new price isn't recurring, is archived, is in another currency, or its interval doesn't combine.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.
subscription_meter_conflict409Another live item of the customer already bills this meter.Change the existing subscription's metered item instead, or cancel it first. A customer has at most one active metered item per meter.
subscription_change_invalid400A change waits for its invoice to be paid, or pending_if_incomplete was asked for a send_invoice subscription.Check details.reason and adjust the request.
coupon_not_redeemable409The coupon is archived, expired or used up.Check details.reason: reactivate an archived coupon, or create a new coupon when it expired or reached its redemption limit.
promotion_code_not_redeemable409The promotion code can't be used (details.reason).Check details.reason and tell the customer: the code may have expired, be used up, belong to another customer, need a larger purchase or be for first-time customers only.
discount_already_applied409The coupon is already applied to this subscription.End the existing discount first (discounts.delete), or apply a different coupon. The same coupon doesn't stack with itself.
registration_required422An invoice issued now can't be taxed: the seller isn't registered where the customer is (a new sale).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.
jurisdiction_blocked422The customer's location is blocked for new sales.This jurisdiction isn't covered by usable tax knowledge for this seller. Contact support if you need to sell there.
evidence_conflict422The customer's stored location evidence isn't enough to tax an invoice issued now (§13.3).Collect another piece of location evidence (billing address, card country, or the customer's confirmation of their country) and quote again.
permission_denied403You can't see this brand's subscriptions (subscriptions:read).Ask an organization owner or admin for a role or scope that allows this action.

It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, scope_required, idempotency_key_required, dry_run_not_supported, rate_limited, internal_error.