# Cancel your subscription

> The portal.subscriptions.cancel operation: POST /api/v1/portal/me/subscriptions/{subscriptionId}/cancel in the REST API and the portal_subscriptions_cancel tool in the MCP server.

Cancels the signed-in customer's subscription as the brand's portal settings say: at the end of the paid period
(`cancelBehavior: period_end`, the default; it can be reactivated until then), or at once (`immediate`: usage
and pending items so far are invoiced, the unused time isn't credited). When the brand asks why
(`collectCancelReason`), the customer may give a reason (`feedback`) and a comment; they're kept on the
subscription for the brand. Recorded as canceled by the customer. Emits `subscription.cancel_scheduled` or
`subscription.canceled`. Customer portal only (`portal:write`).

## At a glance

- **REST:** `POST /api/v1/portal/me/subscriptions/{subscriptionId}/cancel`
- **MCP tool:** `portal_subscriptions_cancel`
- **Operation ID:** `portal.subscriptions.cancel`
- **Scope:** Brand
- **Permission:** `portal:write`
- **Risk tier:** `financial`
- **Dry run:** Supported (send `Ownrecord-Dry-Run: true`)
- **Idempotency:** `Idempotency-Key` required
- **Modes:** Test and live
- **Authentication:** A customer signed in to the customer portal (API keys can't call it)
- **Since:** M9

## Request

### Path parameters

| Field            | Type       | Description                          |
| ---------------- | ---------- | ------------------------------------ |
| `subscriptionId` | `sub_…` ID | One of your subscriptions (`sub_…`). |

### Body

A JSON object (`Content-Type: application/json`):

| Field             | Type                                                                                                                                | Required | Description                                                                                        |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
| `expectedVersion` | integer (at least 1)                                                                                                                | No       | The subscription's `version` you saw (the preview's); if it changed meanwhile, `version_conflict`. |
| `feedback`        | one of `too_expensive`, `missing_features`, `switched_service`, `unused`, `customer_service`, `too_complex`, `low_quality`, `other` | No       | Why you're leaving, when the brand asks (`collectCancelReason`).                                   |
| `comment`         | string                                                                                                                              | No       | Anything you'd like to tell the brand, when it asks.                                               |

## Response

`200 OK` with a JSON object:

| Field                            | Type                                                                                                        | Description                                                                               |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `id`                             | `sub_…` ID                                                                                                  |                                                                                           |
| `object`                         | `"portal_subscription"`                                                                                     |                                                                                           |
| `status`                         | one of `incomplete`, `incomplete_expired`, `trialing`, `active`, `past_due`, `unpaid`, `paused`, `canceled` |                                                                                           |
| `currency`                       | string                                                                                                      |                                                                                           |
| `items`                          | array of object                                                                                             |                                                                                           |
| `items[].priceId`                | `price_…` ID                                                                                                |                                                                                           |
| `items[].productId`              | `prod_…` ID                                                                                                 |                                                                                           |
| `items[].productName`            | string                                                                                                      |                                                                                           |
| `items[].priceName`              | string or null                                                                                              | The price's nickname, e.g. “Pro, monthly”.                                                |
| `items[].interval`               | object                                                                                                      |                                                                                           |
| `items[].interval.interval`      | one of `day`, `week`, `month`, `year`                                                                       |                                                                                           |
| `items[].interval.intervalCount` | integer                                                                                                     |                                                                                           |
| `items[].usageType`              | one of `licensed`, `metered`                                                                                |                                                                                           |
| `items[].unitAmount`             | money or null                                                                                               | A per-unit price's amount per unit and period (null for tiered and metered prices).       |
| `items[].taxBehavior`            | one of `inclusive`, `exclusive`                                                                             | `exclusive`: tax is added on the invoice.                                                 |
| `items[].id`                     | `si_…` ID                                                                                                   |                                                                                           |
| `items[].quantity`               | integer                                                                                                     |                                                                                           |
| `items[].amount`                 | money or null                                                                                               | The item's full-period amount at its quantity, before discounts (null for metered items). |
| `collectionMethod`               | one of `charge_automatically`, `send_invoice`                                                               | `charge_automatically` (your card is charged) or `send_invoice` (paid by bank transfer).  |
| `paymentMethodId`                | `pm_…` ID or null                                                                                           | The card this subscription charges (null: your default card).                             |
| `currentPeriodStart`             | date-time                                                                                                   |                                                                                           |
| `currentPeriodEnd`               | date-time                                                                                                   |                                                                                           |
| `trialEnd`                       | date-time or null                                                                                           |                                                                                           |
| `cancelAtPeriodEnd`              | boolean                                                                                                     |                                                                                           |
| `cancelAt`                       | date-time or null                                                                                           |                                                                                           |
| `canceledAt`                     | date-time or null                                                                                           |                                                                                           |
| `endedAt`                        | date-time or null                                                                                           |                                                                                           |
| `cancellation`                   | object or null                                                                                              |                                                                                           |
| `discounts`                      | array of object                                                                                             |                                                                                           |
| `discounts[].id`                 | `disc_…` ID                                                                                                 |                                                                                           |
| `discounts[].name`               | string                                                                                                      | The coupon's name.                                                                        |
| `discounts[].percentOffPpm`      | string or null                                                                                              | Parts per million as a string (20% = "200000").                                           |
| `discounts[].amountOff`          | money or null                                                                                               |                                                                                           |
| `discounts[].start`              | date-time                                                                                                   |                                                                                           |
| `discounts[].end`                | date-time or null                                                                                           | When it stops applying (null: for as long as the subscription runs).                      |
| `scheduledChange`                | object or null                                                                                              | A change that applies at `effectiveAt` (a downgrade waits for the period end).            |
| `actions`                        | object                                                                                                      | What you can do with this subscription now, as the brand's portal settings allow.         |
| `actions.switch_plan`            | boolean                                                                                                     |                                                                                           |
| `actions.change_quantity`        | boolean                                                                                                     |                                                                                           |
| `actions.apply_promotion_code`   | boolean                                                                                                     |                                                                                           |
| `actions.cancel`                 | boolean                                                                                                     |                                                                                           |
| `actions.reactivate`             | boolean                                                                                                     |                                                                                           |
| `actions.change_payment_method`  | boolean                                                                                                     |                                                                                           |
| `cancelBehavior`                 | one of `period_end`, `immediate`                                                                            | How cancelling here ends the subscription.                                                |
| `collectCancelReason`            | boolean                                                                                                     | Cancelling asks why (optional answers).                                                   |
| `version`                        | integer                                                                                                     | Send it back as `expectedVersion` to change what you saw.                                 |
| `createdAt`                      | date-time                                                                                                   |                                                                                           |

## Examples

### Cancel at the end of the period, too expensive

```http
POST /api/v1/portal/me/subscriptions/sub_01m4tx5nj8emwbyf7y9vcf48c0/cancel
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json

{
  "expectedVersion": 4,
  "feedback": "too_expensive",
  "comment": "Back when my project starts again."
}
```

Response (`200`):

```json
{
  "id": "sub_01m4tx5nj8emwbyf7y9vcf48c0",
  "object": "portal_subscription",
  "status": "active",
  "currency": "EUR",
  "items": [
    {
      "priceId": "price_01m4tx5nj8emwbyf7y9vcf48c2",
      "productId": "prod_01m4tx5nj8emwbyf7y9vcf48c4",
      "productName": "Genno Plus",
      "priceName": "Plus, monthly",
      "interval": {
        "interval": "month",
        "intervalCount": 1
      },
      "usageType": "licensed",
      "unitAmount": {
        "amount": "9000000",
        "currency": "EUR"
      },
      "taxBehavior": "inclusive",
      "id": "si_01m4tx5nj8emwbyf7y9vcf48c1",
      "quantity": 1,
      "amount": {
        "amount": "9000000",
        "currency": "EUR"
      }
    }
  ],
  "collectionMethod": "charge_automatically",
  "paymentMethodId": null,
  "currentPeriodStart": "2026-10-20T09:00:00.000Z",
  "currentPeriodEnd": "2026-11-20T10:00:00.000Z",
  "trialEnd": null,
  "cancelAtPeriodEnd": true,
  "cancelAt": "2026-11-20T10:00:00.000Z",
  "canceledAt": null,
  "endedAt": null,
  "cancellation": {
    "by": "customer",
    "feedback": "too_expensive",
    "comment": "Back when my project starts again."
  },
  "discounts": [],
  "scheduledChange": null,
  "actions": {
    "switch_plan": false,
    "change_quantity": false,
    "apply_promotion_code": false,
    "cancel": false,
    "reactivate": true,
    "change_payment_method": true
  },
  "cancelBehavior": "period_end",
  "collectCancelReason": true,
  "version": 5,
  "createdAt": "2026-09-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 a reason was sent to a brand that doesn't ask for one.                                                                                                             | 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         | You have no such subscription (another customer's reads the same).                                                                                                                          | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode.                                                                                                                                                           |
| [`portal_action_not_allowed`](https://ownrecord.io/docs/reference/errors.md#portal_action_not_allowed)     | 403         | The brand's portal settings or the subscription's state don't allow cancelling: cancelling is off, a cancellation is already scheduled, or the status can't be canceled (`details.action`). | The brand's portal settings don't allow it for this subscription now (`details.action`): contact the brand.                                                                                                                                                    |
| [`subscription_status_invalid`](https://ownrecord.io/docs/reference/errors.md#subscription_status_invalid) | 409         | The subscription can't be canceled in its status.                                                                                                                                           | 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. |
| [`version_conflict`](https://ownrecord.io/docs/reference/errors.md#version_conflict)                       | 409         | The subscription changed since you saw it (`expectedVersion`); look again.                                                                                                                  | Fetch the object again and retry the change.                                                                                                                                                                                                                   |
| [`jurisdiction_blocked`](https://ownrecord.io/docs/reference/errors.md#jurisdiction_blocked)               | 422         | The brand can't sell in your country now (an invoice due now can't be issued).                                                                                                              | This jurisdiction isn't covered by usable tax knowledge for this seller. Contact support if you need to sell there.                                                                                                                                            |
| [`registration_required`](https://ownrecord.io/docs/reference/errors.md#registration_required)             | 422         | The brand can't invoice your country yet (an invoice due now can't be issued).                                                                                                              | 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.                                                      |
| [`livemode_not_allowed`](https://ownrecord.io/docs/reference/errors.md#livemode_not_allowed)               | 403         | The portal session is in the other mode than the request.                                                                                                                                   | Use test mode, or credentials for the target's mode. Developers need live access granted by an organization owner or admin.                                                                                                                                    |
| [`permission_denied`](https://ownrecord.io/docs/reference/errors.md#permission_denied)                     | 403         | The caller isn't a customer signed in to this brand's portal (operators and API keys use the subscription and wallet operations).                                                           | 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), [`rate_limited`](https://ownrecord.io/docs/reference/errors.md#rate_limited), [`internal_error`](https://ownrecord.io/docs/reference/errors.md#internal_error).
