> ## Documentation Index
> Fetch the complete documentation index at: https://docs.saasybill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# End and restore a subscription

> Cancel a subscription now or at period end, and undo it

<Info>
  This page covers the API. To end or restore a subscription in the app, see [Subscriptions](/billing-workspace/subscriptions-workspace). For the lifecycle, see [Subscriptions explained](/core/subscriptions).
</Info>

Both endpoints need the `subscriptions:write` scope. Both return the updated [subscription](/api-reference/objects#subscription).

***

## End a subscription

`POST /v1/subscriptions/{id}/end` takes a single required field, `when`.

| `when` | What happens |
| - | - |
| `period_end` | The subscription runs to its renewal date and doesn't renew. It stays `active` with `cancel_at_period_end: true` and an `end_date`, then becomes `complete`. |
| `now` | The subscription ends immediately and its status becomes `ended`. |

<Note>
  Ending a subscription doesn't refund or credit the current period. The customer keeps what they've paid for.
</Note>

### End at period end

```json theme={null}
{ "when": "period_end" }
```

### End now

With `now` you can also clean up the subscription's invoices in Xero. Both options default to `false`, so Saasybill leaves your accounting documents alone unless you say otherwise.

```json theme={null}
{
  "when": "now",
  "delete_unsent_invoices": true,
  "void_approved_invoices": false
}
```

| Field | Description |
| - | - |
| `delete_unsent_invoices` | Also delete the subscription's invoices that were never sent, in Xero. |
| `void_approved_invoices` | Also void the subscription's approved, unpaid invoices in Xero. |

Both are refused, with `422`, when `when` is `period_end`. Invoices are only removed when a subscription ends now.

<Warning>
  Invoices are removed in Xero first, one at a time, and the subscription ends only if they all go. If Xero refuses one, the subscription keeps running and the API answers `409 invalid_state`. Invoices already removed stay removed.
</Warning>

<CodeGroup>
  ```bash cURL theme={null}
  curl https://app.saasybill.com/api/v1/subscriptions/9d4e2b7a-13c5-4f8e-b6a0-2e1d7c9f5b33/end \
    -H "Authorization: Bearer $SAASYBILL_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: cancel_acme_2026-10-12" \
    -d '{ "when": "period_end" }'
  ```

  ```javascript Node.js theme={null}
  await fetch(`https://app.saasybill.com/api/v1/subscriptions/${subscriptionId}/end`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SAASYBILL_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `cancel_${accountId}_${requestId}`,
    },
    body: JSON.stringify({ when: "period_end" }),
  });
  ```
</CodeGroup>

### Ending near renewal

If the next period's renewal invoice has already been raised, a subscription that won't renew has no use for it. Ending the subscription, now or at period end, voids the invoice in Xero if it was approved, or deletes it. Restoring later raises the invoice again.

The end is refused while that invoice has a payment or credit on it, because the customer has already paid for the next period. The answer is `409 invalid_state`. To end it after that period, change **End after** in the subscription's details in the app.

### When it can't be ended

A subscription that can't be ended answers `409 invalid_state`, with a message that says why. Examples include a subscription that has already ended, and Setup Mode when you ask to remove invoices, because nothing can be changed in Xero.

***

## Restore a subscription

`POST /v1/subscriptions/{id}/restore` undoes an end. It takes an empty JSON object.

```json theme={null}
{}
```

It restores:

* a subscription set to end at period end, which goes back to renewing
* an `ended` subscription, **before its renewal date**

```bash theme={null}
curl https://app.saasybill.com/api/v1/subscriptions/9d4e2b7a-13c5-4f8e-b6a0-2e1d7c9f5b33/restore \
  -H "Authorization: Bearer $SAASYBILL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: restore_acme_2026-10-13" \
  -d '{}'
```

### When it can't be restored

A subscription that can't be restored answers `409 invalid_state`, with a message that says why.

| Reason | What to do |
| - | - |
| The renewal date has passed. | The subscription can no longer be restored. Create a new one. |
| The customer isn't active. | Restore the customer in Xero, then try again. |
| The plan isn't active or legacy. | Change the plan's status in Saasybill, then try again. |
| An add-on on the subscription isn't active or legacy. | Change the add-on's status in Saasybill, then try again. |

***

## Tell your platform

Both actions send webhooks, so you can confirm the outcome without polling.

| Action | Events |
| - | - |
| End now | `subscription.ended` |
| End at period end | `subscription.updated` now, and `subscription.ended` when it completes |
| Restore | `subscription.updated` |

See [Webhook events](/api-reference/webhooks/events).
