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
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
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 numericid. Find the active paid subscription in listCustomerSubscriptions and offer the control only when its cancellation.manageable is true:
{ "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.