# Idempotency and retries

Every write takes an Idempotency-Key header, so you can repeat a request after a timeout without ordering twice or rebooting twice. This page says exactly what a repeat returns.

## The header

```
Idempotency-Key: 5f0c2a8e-8a9b-4d7e-9c55-2f1d3b7a6e10
```

- 8 to 64 characters: letters, digits, `-` and `_`. A UUID works.
- Required on every POST and DELETE. The only exception is an order dry run (`POST /orders?dry_run=true`), which changes nothing.
- Missing or malformed: `400 idempotency_key_required`. Nothing is done.

Make one new key for each thing you mean to do once: one order, one reboot, one rename. Keep it until you have the answer.

## What a repeat returns

Idempotency keys belong to the API key that sent them. Two API keys can use the same Idempotency-Key without meeting.

| You send | You get |
|---|---|
| A new Idempotency-Key | The request runs. Its answer is stored. |
| The same Idempotency-Key, the same body, the same server, and the first request has finished | The stored answer again, with the same status code and body. Nothing runs twice. A stored refusal (for example `409 sold_out`) comes back as the same error. |
| The same Idempotency-Key while the first request is still running | `409 in_progress` with `details.retry_after` (seconds). Ask again with the same key after that. |
| The same Idempotency-Key with a different body, or for a different server | `422 idempotency_conflict`. Nothing runs. Use a new key for a new request. |
| The same Idempotency-Key after the first attempt ended `409 in_progress` or `503 unavailable` | The request runs again. Those two outcomes mean nothing was done, so a retry is allowed. |

Example: a reboot whose answer was lost on the way.

```bash
KEY=$(uuidgen)
curl -sS -X POST https://api.redswitches.com/v1/servers/srv_123/power \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"action":"reboot"}'
# the connection dropped: send exactly the same request again
curl -sS -X POST https://api.redswitches.com/v1/servers/srv_123/power \
  -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{"action":"reboot"}'
```

```ts
import { randomUUID } from "node:crypto";

const idempotencyKey = randomUUID();
// the SDK retries with the same key on network errors, 429, 503 and in_progress
const action = await rs.power.run("srv_123", { action: "reboot" }, { idempotencyKey });
```

```python
import uuid

key = str(uuid.uuid4())
action = client.power.run("srv_123", action="reboot", idempotency_key=key)
```

```go
key := uuid.NewString()
action, err := client.Power.Run(ctx, "srv_123", "reboot", &redswitches.RequestOptions{IdempotencyKey: key})
```

```json
{
  "id": "act_42",
  "kind": "power",
  "server_id": "srv_123",
  "action": "reboot",
  "state": "queued",
  "poll_after": 10
}
```

Both calls return the same action. The server reboots once.

## When to retry

Retry only when you do not know the outcome or the API asked you to wait. Always reuse the same Idempotency-Key and the same body.

| Situation | Retry? |
|---|---|
| Network error or timeout, no answer | Yes, same key. |
| `429 rate_limited` | Yes, after the `Retry-After` header's seconds. |
| `409 in_progress` | Yes, after `details.retry_after` seconds. |
| `503 unavailable` | Yes, with a growing pause (for example 2, 4, 8 seconds). |
| `500 internal_error` | Not blindly. For an order, read [Orders](orders.md) first: list your orders to see what happened, then contact support with the request id. |
| Any other `4xx` | No. Fix the request. A repeat with the same key returns the same answer. |
| `409 order_under_review` | No. Staff are checking the order. Do not place it again. |

The SDKs do this for you: each write gets a fresh Idempotency-Key unless you pass one, and they retry only on network errors, 429, 503 and `in_progress`, always with the same key.

## Orders in particular

If an order request times out, send it again with the same Idempotency-Key and the same body. You get the order that was placed (`201`), the refusal that ended it, or `409 in_progress` while it is still running. You never get a second order. See the [Ordering guide](guide-ordering.md).
