# PATCH /api/v2/customer-subscriptions/{customer_subscription}

Update a customer subscription

- MCP id: customer-subscriptions.update
- Scope: api:write:customer_subscriptions
- Permission: customer_subscriptions::update
- Needs the billing feature on your organisation
- Rate cost: 1 unit, write bucket. A write costs the same whatever `view` you ask for.
- Cache-Control: private, no-store

Partially updates a subscription: absent keys are untouched.

Send `If-Match` with the `ETag` from your last read to detect a concurrent edit, which answers `412`. Switching `billing_interval` to one-time also sets `auto_renew` to `false`, whatever is sent alongside it; this holds on every save. Clearing `next_invoice_date` to `null` takes the subscription out of the renewal run without cancelling it. Lines are edited through their own sub-resource, never from here.

## Parameters
- customer_subscription (integer, path): The CustomerSubscription's numeric id. Accepts a soft-deleted record.
- view (string, optional): Which field set to render.
- trashed (string, optional): Whether soft-deleted rows are included.
- X-Request-Id (header, optional): A correlation id of your own. It is recorded in our logs next to the server's request id but never returned: the response header always carries the server's id. A value that does not match the pattern is discarded silently, never rejected.
- If-Match (header, optional): The ETag a write is conditional on. A stale validator is 412 precondition-failed.

## Request body
- customer_id (integer → Customer.id, optional): Customer this subscription belongs to.
- subscription_type_id (integer → SubscriptionType.id, optional): Subscription type this subscription is an instance of.
- description (string or null, optional): What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to.
- status (string, optional): Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert.
- billing_interval (string, optional): How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to.
- starts_at (date (YYYY-MM-DD), optional): First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort.
- next_invoice_date (date (YYYY-MM-DD) or null, optional): First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way.
- cancelled_at (date (YYYY-MM-DD) or null, optional): Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it.
- expires_at (date (YYYY-MM-DD) or null, optional): Day the subscription runs out, `null` when it is open-ended.
- auto_renew (boolean, optional): Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing.

Unknown keys are refused with `422` validation-failed, naming the key.

## Response 200
- id (integer, required): Unique identifier.
- customer_id (integer, required): Customer this subscription belongs to.
- customer (Ref or CustomerSummary, required): The customer this subscription belongs to. Expanding it with `?include=customer` additionally requires the `customers::read` permission and a token scope covering the customers prefix.
- subscription_type_id (integer, required): Subscription type this subscription is an instance of.
- subscription_type (Ref or SubscriptionTypeSummary, required): The subscription type this subscription is an instance of, which is also where its displayed name comes from. Expanding it with `?include=subscription_type` additionally requires the `subscription_types::read` permission and a token scope covering the `subscription_types` prefix.
- description (string or null, required, multiline): What this subscription covers, as free text. This is the field `?q=` searches, and it is also what a generated invoice line falls back to.
- status (string, required): Lifecycle status. Only an active subscription is picked up by the renewal run; the other three are inert.
- billing_interval (string, required): How often the subscription is invoiced. one-time forces `auto_renew` to `false`, because a one-off has no next period to move on to.
- starts_at (date (YYYY-MM-DD), required): First day the subscription runs from, as a zone-free calendar date. Newest first is the default sort.
- next_invoice_date (date (YYYY-MM-DD) or null, required): First day of the period to be invoiced next. Omit it on create and the server fills it with `starts_at`, so the first period is billable; the renewal run advances it by one interval each time it invoices. Null makes the subscription invisible to that run (it is skipped, silently), so a subscription that should never auto-invoice is expressed this way.
- cancelled_at (date (YYYY-MM-DD) or null, required): Day the subscription was cancelled, `null` while it has not been. It is a record of the cancellation date only: nothing derives status from it.
- expires_at (date (YYYY-MM-DD) or null, required): Day the subscription runs out, `null` when it is open-ended.
- auto_renew (boolean, required): Whether the renewal run may invoice this subscription again. A one-time subscription is always `false`: it is forced on every save, so sending `true` together with `billing_interval=one-time` succeeds and comes back as `false` rather than failing.
- exact_subscription_id (string or null ≤ 255, required, server-assigned): Exact Online subscription this one maps to, by Exact's own external identifier, not by a local id. Written by the sync; `null` when the subscription has never been synced.
- last_invoiced_at (date (YYYY-MM-DD) or null, required, server-assigned): Day the subscription was last invoiced, `null` until it has been. Maintained by the renewal run.
- created_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Creation timestamp.
- updated_at (timestamp (YYYY-MM-DDThh:mm:ssZ), required): Last-write timestamp; use for incremental sync. It also feeds the `ETag` revision, because a customer subscription has no `version` field.
- deleted_at (timestamp (YYYY-MM-DDThh:mm:ssZ) or null, required): Soft-delete timestamp, `null` while live.
- items_count (integer ≥ 0, required): Number of lines on this subscription.
- links (CustomerSubscriptionLinks, required)
  - self (url, required): The canonical URI of this record.
  - items (url, required): Lines on this subscription.

## Errors
- 403 forbidden-by-policy | insufficient-permission | insufficient-token-scope: Forbidden.
- 412 precondition-failed: Precondition failed.
- 415 unsupported-media-type: Unsupported media type.
- 422 unprocessable-relationship | validation-failed: Unprocessable content.
- 400 idempotency-key-required | invalid-cursor | invalid-query-parameter | malformed-request | unsupported-parameter: Bad request.
- 401 token-expired | unauthenticated: Unauthenticated.
- 404 endpoint-not-found | resource-not-found: Not found.
- 406 not-acceptable: Not acceptable.
- 429 rate-limit-exceeded: Too many requests.
- 500 internal-error: Server error.
- 503 tenant-unavailable: Service unavailable.

## Example
```bash
curl -X PATCH \
  'https://<your-url>/api/v2/customer-subscriptions/1' \
  -H 'Authorization: Bearer <token>' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -H 'If-Match: *' \
  -d '{
    "status": "cancelled",
    "cancelled_at": "2026-09-30",
    "auto_renew": false
  }'
```
