API ReferenceSubscriptions

Preview a plan change

Preview the billing impact of a plan change before confirming it.

A plan change belongs to the customer, and a store token previews it on their behalf. preview_payload contains the provider's own preview, so its keys differ per provider. Stripe returns the proration as an upcoming invoice. A subscription the store bills in house also prices the proration, and bills it as a separate proration order on confirmation. PayPal returns no pricing and reports that the change needs the buyer's approval.

Stripe and in-house previews also say what the customer pays and when:

  • payment_required: true means amount_due is due today. Stripe charges it when you confirm. In house, the confirmation returns an approval_url where the customer pays it. On Stripe this happens with proration_behavior: invoice_now, and whenever the change starts a new billing period, such as monthly to yearly or a free plan to a paid one.
  • payment_required: false means nothing is charged today. An amount_due in a Stripe preview is then the next invoice, prorations included, which Stripe collects on renewal_date.
  • renewal_date is when the subscription renews next after the change: the end of the current period, or of the new one when a Stripe change starts one.

A plan change during a trial never charges today.

A plan change ends a subscription discount. While the subscription still has discounted payments left and will renew, so it isn't set to cancel, the preview carries discount_warning, a sentence to show the customer before they confirm, such as Changing plan ends your discount; after this change you'll pay $20.00 / month. A Stripe preview's amounts already leave the discount out. In house, the unused part of the current period is credited at the discounted price the customer paid for it.

POST
/v2/subscriptions/{productSubscription}/actions/change-plan/preview

Authorization

AuthorizationBearer <token>

Provide your SellApp API key as a bearer token.

In: header

Path Parameters

productSubscription*integer

The productSubscription path parameter.

Header Parameters

Idempotency-Key?string

A caller-generated key, unique per store and subscription lifecycle request. Sending it here is the same as sending idempotency_key in the body.

Lengthlength <= 128
Idempotency-Key?string

Caller-generated key for safely retrying this operation. The JSON idempotency_key field is also accepted; when both are sent, the header takes precedence.

Lengthlength <= 128
X-STORE?string

Store slug. Required for OAuth access tokens. API keys may omit it to use their current store, or the first accessible store when no current store is selected.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://sell.app/api/v2/subscriptions/0/actions/change-plan/preview" \  -H "X-STORE: launch-lab" \  -H "Content-Type: application/json" \  -d '{    "target_variant_id": 4321,    "effective_timing": "immediate",    "proration_behavior": "provider_default"  }'
{
  "data": {
    "preview_token": "subprev_9c4b2f",
    "expires_at": "2026-07-03T12:30:00.000000Z",
    "preview_payload": {
      "provider": "stripe",
      "invoice_id": "upcoming_in_1QwertyExample",
      "amount_due": 2920,
      "amount_remaining": 2920,
      "subtotal": 2920,
      "total": 2920,
      "currency": "usd",
      "line_items": [
        {
          "id": "il_tmp_1QwertyUnused",
          "amount": -919,
          "currency": "usd",
          "description": "Unused time on Design kit monthly after 03 Jul 2026",
          "proration": true,
          "period_start": "2026-07-03T12:00:00+00:00",
          "period_end": "2026-08-01T00:00:00+00:00"
        },
        {
          "id": "il_tmp_1QwertyRemaining",
          "amount": 1839,
          "currency": "usd",
          "description": "Remaining time on Design kit pro after 03 Jul 2026",
          "proration": true,
          "period_start": "2026-07-03T12:00:00+00:00",
          "period_end": "2026-08-01T00:00:00+00:00"
        },
        {
          "id": "il_tmp_1QwertyNextPeriod",
          "amount": 2000,
          "currency": "usd",
          "description": "1 × Design kit pro (at $20.00 / month)",
          "proration": false,
          "period_start": "2026-08-01T00:00:00+00:00",
          "period_end": "2026-09-01T00:00:00+00:00"
        }
      ],
      "subscription_id": "sub_1QwertyExample",
      "subscription_item_id": "si_QwertyExample",
      "target_price_id": "price_1QwertyPro",
      "proration_behavior": "create_prorations",
      "proration_date": 1783080000,
      "renewal_date": "2026-08-01T00:00:00+00:00",
      "payment_required": false,
      "provider_previewed_monetary_proration": true
    },
    "request_payload": {
      "source_variant_id": 99,
      "target_variant_id": 4321,
      "effective_timing": "immediate",
      "proration_behavior": "provider_default"
    },
    "customer_message": null,
    "seller_message": null
  },
  "message": "Subscription plan change preview created."
}
{
  "type": "invalid_request_error",
  "code": "bad_request",
  "message": "The request could not be understood.",
  "status": 400,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#bad-request"
}
{
  "type": "authentication_error",
  "code": "unauthenticated",
  "message": "Unauthenticated.",
  "status": 401,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#unauthenticated"
}
{
  "type": "authorization_error",
  "code": "forbidden",
  "message": "This action is unauthorized.",
  "status": 403,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#forbidden"
}
{
  "type": "not_found_error",
  "code": "resource_not_found",
  "message": "Not Found.",
  "status": 404,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#resource-not-found"
}
{
  "type": "validation_error",
  "code": "validation_failed",
  "message": "The selected product variant id is invalid.",
  "status": 422,
  "param": "product_variant_id",
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#validation-failed",
  "errors": {
    "product_variant_id": [
      "The selected product variant id is invalid."
    ]
  }
}
{
  "type": "rate_limit_error",
  "code": "rate_limit_exceeded",
  "message": "Too Many Attempts.",
  "status": 429,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#rate-limit-exceeded"
}
{
  "type": "api_error",
  "code": "api_error",
  "message": "Server Error",
  "status": 500,
  "param": null,
  "request_id": "01992a65-e064-71ba-b38f-902b7966a6be",
  "docs_url": "https://sell.app/docs/api/errors#api-error"
}