# Cancellation

Ask for a server to be cancelled at the end of its billing period, check the request, or ask for it to be withdrawn. Requesting and withdrawing need the `cancel` permission; reading needs `read`.

> **Beta.** Cancellation requests through the API are built and switched on by RedSwitches; they may not be available yet. Until then, cancel in the client area. The endpoints and fields below are the beta contract and may still change.

## How it works

- A cancellation request through the API is always **end of billing period**. The server keeps running, and is billed as normal, until its next due date. It is not removed at once.
- The account owner is emailed as soon as a request is made.
- The `cancel` permission is chosen when a key is made and is limited by the key's server list, like `power`.
- A server must be active or suspended. Pending or ended servers cannot be cancelled through the API.
- At most 2 requests per account per day. Our staff are told when the ceiling is reached.
- While a "keep this server" request for the server is less than a day old, a cancellation request answers `409 server_busy`.
- Reading the state (`GET`) needs only `read`.

## Request cancellation

`POST /servers/{id}/cancel-request`. Needs an `Idempotency-Key`.

| Body field | Required | Rule |
|---|---|---|
| `reason` | no | Why you are cancelling. Up to 255 characters, no control characters and no `<`, `>`, `{` or `}`. |

The body may be left out or be `{}`.

```bash
curl -sS -X POST https://api.redswitches.com/v1/servers/srv_123/cancel-request \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"reason":"Moving this workload elsewhere"}'
```

```ts
const req = await rs.cancellation.request("srv_123", { reason: "Moving this workload elsewhere" });
```

```python
req = client.cancellation.request("srv_123", reason="Moving this workload elsewhere")
```

```go
req, err := client.Cancellation.Request(ctx, "srv_123", &redswitches.CancelRequest{Reason: "Moving this workload elsewhere"})
```

```json
{
  "server_id": "srv_123",
  "state": "requested",
  "requested": "2026-11-15T09:30:00Z",
  "effective": "2026-12-15",
  "type": "end_of_billing_period"
}
```

The answer is `201 Created` for a new request. If a request already exists for the server, the answer is `200` with that request as it is; nothing new is filed.

## Check the request

`GET /servers/{id}/cancel-request`.

```bash
curl -sS https://api.redswitches.com/v1/servers/srv_123/cancel-request \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY"
```

```ts
const state = await rs.cancellation.get("srv_123");
```

```python
state = client.cancellation.get("srv_123")
```

```go
state, err := client.Cancellation.Get(ctx, "srv_123")
```

```json
{
  "server_id": "srv_123",
  "state": "none",
  "requested": null,
  "effective": null,
  "type": null
}
```

| Field | Type | Meaning |
|---|---|---|
| `server_id` | string | The server. |
| `state` | string | `none` (no request), `requested`, or `withdrawal_requested`. |
| `requested` | string or null | When the request was made (UTC). |
| `effective` | string or null | The date the server ends (`YYYY-MM-DD`). |
| `type` | string or null | `end_of_billing_period` for every request made through the API. `immediate` can show for a request made another way. Null when there is no request. |

## Ask to withdraw

`DELETE /servers/{id}/cancel-request` asks RedSwitches staff to withdraw the request. Needs an `Idempotency-Key` and no body. Nothing is un-cancelled automatically: staff handle it, as for a withdrawal asked for in the client area. Asking again does not file a second withdrawal. When the server has no cancellation request, the answer is `404 not_found`.

```bash
curl -sS -X DELETE https://api.redswitches.com/v1/servers/srv_123/cancel-request \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"
```

```ts
const state = await rs.cancellation.withdraw("srv_123");
```

```python
state = client.cancellation.withdraw("srv_123")
```

```go
state, err := client.Cancellation.Withdraw(ctx, "srv_123")
```

```json
{
  "server_id": "srv_123",
  "state": "withdrawal_requested",
  "requested": "2026-11-15T09:30:00Z",
  "effective": "2026-12-15",
  "type": "end_of_billing_period"
}
```

The answer is `200`.

## Errors

| Status | Code | Meaning |
|---|---|---|
| 403 | `permission_denied` | The key does not have `cancel`, or this server is not on its server list. |
| 404 | `not_found` | No such server on this account, or (on `DELETE`) no cancellation request to withdraw. |
| 409 | `server_not_active` | The server is not active or suspended. |
| 409 | `server_busy` | A "keep this server" request for it is less than a day old. |
| 429 | `action_limit` | More than 2 cancellation requests for the account in a day (`details.per: "account_day"`). |
