Cancel a subscription
The subscriptions.cancel operation: POST /api/v1/subscriptions/{id}/cancel in the REST API and the subscriptions_cancel tool in the MCP server.
/api/v1/subscriptions/{id}/cancelEnds a subscription.
mode: at_period_end(default): it stays active until the current period ends (cancelAt), then ends;subscriptions.reactivatewithdraws the cancellation until then. Emitssubscription.cancel_scheduled.mode: now: it ends at once. Withprorate: truethe unused time of the current period goes to the customer's credit balance (at the prices' amounts, discounts taken into account). WithinvoiceNow(default) the metered usage up to now and any pending items are invoiced in a final invoice, which the credit balance reduces. An incomplete subscription's unpaid first invoice is voided. Emitssubscription.canceled.
mode: now with refund: true refunds the unused time of the current period to the customer's card instead:
every paid invoice of the subscription with time left gets a credit note at its original tax rates and a
refund (refunds.create with subscription_prorated; you need the refund permission, and support's refund
limit applies). An invoice paid by bank transfer or from the credit balance can't be refunded that way
(subscription_refund_not_supported: cancel with prorate: true instead).
feedback and comment record why the customer left. The customer's entitlements are recomputed.
The customer is emailed that the subscription is set to end or has ended. reason: replaced says another
subscription of the same customer takes its place (for example an upgrade bought through a new checkout): it is
recorded as cancellation.reason: replaced, the webhook events go out as usual, and the customer gets no
cancellation email, since the new subscription's own emails tell them what runs.
At a glance
- REST:
POST /api/v1/subscriptions/{id}/cancel - MCP tool:
subscriptions_cancel - Operation ID:
subscriptions.cancel - Scope: Brand
- Permission:
subscriptions:write - Risk tier:
financial - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
subscriptions:writescope - Since: M5
Request
Path parameters
| Field | Type | Description |
|---|---|---|
id | sub_… ID |
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
expectedVersion | integer (at least 1) | No | |
mode | one of at_period_end, now | No | at_period_end (default; it can be reactivated until then) or now. |
prorate | boolean | No | now: credit the unused time of the current period to the customer's credit balance. |
refund | boolean | No | now: refund the unused time of the current period to the customer's card, with a credit note (needs the refund permission). |
invoiceNow | boolean | No | now: invoice the metered usage and pending items up to now in a final invoice. |
feedback | one of too_expensive, missing_features, switched_service, unused, customer_service, too_complex, low_quality, other | No | Why the customer is leaving. |
comment | string | No | |
by | one of customer, operator, system | No | Who asked: customer or operator (default operator). |
reason | one of cancellation_requested, replaced | No | cancellation_requested (default; the customer is emailed that it ends) or replaced: another subscription of the same customer takes its place (an upgrade bought through a new checkout), so the customer isn't emailed about this one ending. |
Response
200 OK with a JSON object:
| Field | Type | Description |
|---|---|---|
id | sub_… ID | |
object | "subscription" | |
livemode | boolean | |
entityId | ent_… ID | |
brandId | brd_… ID | |
customerId | cus_… ID | |
status | one of incomplete, incomplete_expired, trialing, active, past_due, unpaid, paused, canceled | incomplete (waiting for the first payment, 23 hours), trialing, active, past_due, unpaid, paused, canceled or incomplete_expired (spec §16.1). |
items | array of object | |
items[].id | si_… ID | |
items[].priceId | price_… ID | |
items[].productId | prod_… ID | |
items[].quantity | integer | |
items[].createdAt | date-time | |
currency | string | |
mrr | money or null | The items' monthly recurring amount (licensed items, before discounts and tax). |
collectionMethod | one of charge_automatically, send_invoice | |
daysUntilDue | integer or null | |
defaultPaymentMethodId | pm_… ID or null | |
billingCycleAnchor | date-time | |
currentPeriodStart | date-time | |
currentPeriodEnd | date-time | |
trialStart | date-time or null | |
trialEnd | date-time or null | |
trialEndBehavior | one of convert, cancel_if_no_payment_method, pause_if_no_payment_method | |
cancelAtPeriodEnd | boolean | |
cancelAt | date-time or null | |
canceledAt | date-time or null | |
endedAt | date-time or null | |
cancellation | object or null | |
pause | object or null | |
discountIds | array of disc_… ID | |
pendingUpdate | object or null | |
latestInvoiceId | inv_… ID or null | |
incompleteExpiresAt | date-time or null | |
testClockId | clk_… ID or null | |
dunning | object or null | Dunning (§16.10) while past due or unpaid; null otherwise. |
usageThreshold | money or null | Unbilled metered usage worth this much is invoiced at once (spec §16.7). |
entitlementGraceUntil | date-time or null | |
metadata | object | |
version | integer | |
createdAt | date-time | |
updatedAt | date-time |
Examples
Cancel at the end of the period
POST /api/v1/subscriptions/sub_01m4tx5nj8emwbyf7y9vcf47z5/cancel
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"feedback": "too_expensive"
}
Response (200):
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"object": "subscription",
"livemode": false,
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47zm",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47z4",
"status": "active",
"items": [
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47za",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
"quantity": 3,
"createdAt": "2026-10-13T12:00:00.000Z"
},
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47zb",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y9",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y1",
"quantity": 1,
"createdAt": "2026-10-13T12:00:00.000Z"
}
],
"currency": "EUR",
"mrr": {
"amount": "87000000",
"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": true,
"cancelAt": "2026-11-13T12:00:00.000Z",
"canceledAt": null,
"endedAt": null,
"cancellation": {
"reason": "cancellation_requested",
"feedback": "too_expensive",
"comment": null,
"by": "operator"
},
"pause": null,
"discountIds": [],
"pendingUpdate": null,
"latestInvoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
"incompleteExpiresAt": null,
"testClockId": null,
"dunning": null,
"usageThreshold": null,
"entitlementGraceUntil": null,
"metadata": {
"crm_account": "A-1042"
},
"version": 3,
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-20T09:00:00.000Z"
}
Cancel now and credit the unused time
POST /api/v1/subscriptions/sub_01m4tx5nj8emwbyf7y9vcf47z5/cancel
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"mode": "now",
"prorate": true
}
Response (200):
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"object": "subscription",
"livemode": false,
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47zm",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47z4",
"status": "canceled",
"items": [
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47za",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
"quantity": 3,
"createdAt": "2026-10-13T12:00:00.000Z"
},
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47zb",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y9",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y1",
"quantity": 1,
"createdAt": "2026-10-13T12:00:00.000Z"
}
],
"currency": "EUR",
"mrr": {
"amount": "87000000",
"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": "2026-10-20T09:00:00.000Z",
"endedAt": "2026-10-20T09:00:00.000Z",
"cancellation": {
"reason": "cancellation_requested",
"feedback": null,
"comment": null,
"by": "operator"
},
"pause": null,
"discountIds": [],
"pendingUpdate": null,
"latestInvoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
"incompleteExpiresAt": null,
"testClockId": null,
"dunning": null,
"usageThreshold": null,
"entitlementGraceUntil": null,
"metadata": {
"crm_account": "A-1042"
},
"version": 3,
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-20T09:00:00.000Z"
}
End a subscription another one replaced
The customer upgraded through a new checkout: the old plan ends now, without an email.
POST /api/v1/subscriptions/sub_01m4tx5nj8emwbyf7y9vcf47z5/cancel
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"mode": "now",
"reason": "replaced"
}
Response (200):
{
"id": "sub_01m4tx5nj8emwbyf7y9vcf47z5",
"object": "subscription",
"livemode": false,
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47zm",
"brandId": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"customerId": "cus_01m4tx5nj8emwbyf7y9vcf47z4",
"status": "canceled",
"items": [
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47za",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y2",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y0",
"quantity": 3,
"createdAt": "2026-10-13T12:00:00.000Z"
},
{
"id": "si_01m4tx5nj8emwbyf7y9vcf47zb",
"priceId": "price_01m4tx5nj8emwbyf7y9vcf47y9",
"productId": "prod_01m4tx5nj8emwbyf7y9vcf47y1",
"quantity": 1,
"createdAt": "2026-10-13T12:00:00.000Z"
}
],
"currency": "EUR",
"mrr": {
"amount": "87000000",
"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": "2026-10-20T09:00:00.000Z",
"endedAt": "2026-10-20T09:00:00.000Z",
"cancellation": {
"reason": "replaced",
"feedback": null,
"comment": null,
"by": "operator"
},
"pause": null,
"discountIds": [],
"pendingUpdate": null,
"latestInvoiceId": "inv_01m4tx5nj8emwbyf7y9vcf47zn",
"incompleteExpiresAt": null,
"testClockId": null,
"dunning": null,
"usageThreshold": null,
"entitlementGraceUntil": null,
"metadata": {
"crm_account": "A-1042"
},
"version": 3,
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-20T09:00:00.000Z"
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | A field is malformed or out of range. | Fix the fields listed in details.issues and send the request again. |
resource_not_found | 404 | No 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. |
version_conflict | 409 | The subscription changed since you read it (expectedVersion), or at the same time; read it and retry. | Fetch the object again and retry the change. |
subscription_status_invalid | 409 | The 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_refund_not_supported | 400 | refund: true, and an invoice with time left wasn't paid by card (bank transfer or credit balance): use prorate: true. | An invoice of the current period wasn't paid by card (bank transfer or the credit balance; details.invoice_id). Cancel with prorate: true to credit the unused time to the customer's credit balance, or without it. |
refund_limit_exceeded | 403 | refund: true by the support role, above the brand's refund limit: finance cancels it. | Ask a member with the finance role (or an API key with refunds:admin) to make it, or refund at most details.limit. The limit is the brand's (brands.policies.supportRefundLimits). |
registration_required | 422 | An 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_blocked | 422 | The 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_conflict | 422 | The 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_denied | 403 | The credentials can't change this brand's subscriptions (subscriptions:write). | 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, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.