Skip to main content
A customer on a free plan, or with no plan, changes plan with attach. A customer on an active paid plan changes plan with the calls on this page. Each one takes an idempotency key that identifies this one intended change: store it and reuse it on every retry, so a retry never charges or refunds twice. One change at a time: while an upgrade is waiting for payment, or a downgrade or cancellation is scheduled, the others return 409 (upgrade_pending, downgrade_scheduled, cancellation_scheduled). Undo the scheduled one first.

Upgrade

Stripe charges the full new price and starts a new monthly period. The old plan’s unspent credits stay until the old period would have ended, and are spent first; packs are untouched. Act on status: The target must be a synced paid plan that costs more (400 ineligible_upgrade), and the Stripe subscription can’t carry discounts (400 ineligible_stripe_subscription). Tax is fine: a taxed subscription stays taxed, and with Automatic tax on, an untaxed one starts being taxed when Stripe knows the customer’s location. On 502 stripe_upgrade_failed or 503 upgrade_reconciliation_required, retry with the same key.

Downgrade

By default the customer keeps the current plan and its credits until the period ends, and the renewal starts the cheaper plan. The response is scheduled, with effectiveAt set to the renewal. Undo it any time before then with cancelScheduledDowngrade({ downgradeId, customerId }); getScheduledDowngrade returns the one that’s pending. With timing: "immediately", the cheaper plan starts now and the renewal date stays. The old plan’s remaining credits end (oldPlanCredits: "keep" leaves them), the new plan’s credits are prorated to the rest of the period, and refund: "prorated" refunds the unused share of the price difference to the card, plus the tax charged on it. The response is applied. Overage from the old plan always bills at its own rate. The target must be a cheaper, synced paid plan (400 ineligible_downgrade). Moving to a free plan isn’t a downgrade: cancel at period end, then attach the free plan.

Cancel at period end

Renewal is set on the subscription’s numeric id. Find the active paid subscription in listCustomerSubscriptions and offer the control only when its cancellation.manageable is true:
The response is { "cancelAtPeriodEnd": true, "cancelsAt": "2026-12-07T07:25:02.000Z" }. Show cancelsAt and a Resume button: the plan and its credits last until then, and Stripe doesn’t charge again. When the period ends, the subscription is canceled, its credits expire, and packs stay. Attach your free plan to give the customer an allowance again. cancellation is null for free plans, pending Checkouts, and when Ledger couldn’t read the subscription from Stripe. 409 subscription_not_manageable means Stripe has a change Ledger won’t override, such as a pause or a custom cancellation date; resolve it in Stripe. Ledger never refunds a cancellation. To end a subscription immediately, cancel it in Stripe and Ledger follows.
Other ways. Customers in the dashboard have a Manage subscription sheet for one-off changes. Over HTTP, see upgrade, downgrade and cancellation. Every error code is in Errors.