# 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.

Ends a subscription.

- `mode: at_period_end` (default): it stays active until the current period ends (`cancelAt`), then ends;
  `subscriptions.reactivate` withdraws the cancellation until then. Emits `subscription.cancel_scheduled`.
- `mode: now`: it ends at once. With `prorate: true` the unused time of the current period goes to the
  customer's credit balance (at the prices' amounts, discounts taken into account). With `invoiceNow` (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. Emits `subscription.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-Key` required
- **Modes:** Test and live
- **Authentication:** API key with the `subscriptions:write` scope
- **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

```http
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`):

```json
{
  "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

```http
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`):

```json
{
  "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.

```http
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`):

```json
{
  "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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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`](https://ownrecord.io/docs/reference/errors.md#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](https://ownrecord.io/docs/reference.md#common-errors): [`api_version_unsupported`](https://ownrecord.io/docs/reference/errors.md#api_version_unsupported), [`invalid_json`](https://ownrecord.io/docs/reference/errors.md#invalid_json), [`unsupported_media_type`](https://ownrecord.io/docs/reference/errors.md#unsupported_media_type), [`authentication_required`](https://ownrecord.io/docs/reference/errors.md#authentication_required), [`api_key_invalid`](https://ownrecord.io/docs/reference/errors.md#api_key_invalid), [`scope_required`](https://ownrecord.io/docs/reference/errors.md#scope_required), [`idempotency_key_required`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_required), [`idempotency_key_reused`](https://ownrecord.io/docs/reference/errors.md#idempotency_key_reused), [`idempotency_in_progress`](https://ownrecord.io/docs/reference/errors.md#idempotency_in_progress), [`approval_required`](https://ownrecord.io/docs/reference/errors.md#approval_required), [`approval_rejected`](https://ownrecord.io/docs/reference/errors.md#approval_rejected), [`approval_expired`](https://ownrecord.io/docs/reference/errors.md#approval_expired), [`stale_approval`](https://ownrecord.io/docs/reference/errors.md#stale_approval), [`approval_policy_denied`](https://ownrecord.io/docs/reference/errors.md#approval_policy_denied), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
