# Power and actions guide

Reboot, power off and power on servers safely from scripts, and follow each action to the end. The endpoint details are on [Actions and power](actions.md).

## Before you start

- The key needs the `power` permission, and the server must be on the key's server list. "All servers" means the servers on the account when the list was saved; a newer server is not covered until the owner updates the list.
- Every power request needs an `Idempotency-Key`.

## The pattern: request, then follow

A power request only queues the action and answers at once with `202` and an action id. The action itself runs moments later. Follow it with `GET /actions/{id}`, waiting `poll_after` seconds before the first look.

```bash
act=$(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: $(uuidgen)" \
  -d '{"action":"reboot"}' | jq -r .id)
sleep 10
until state=$(curl -sS "https://api.redswitches.com/v1/actions/$act" \
    -H "Authorization: Bearer $REDSWITCHES_API_KEY" | jq -r .state) \
  && [ "$state" != queued ] && [ "$state" != running ]; do
  sleep 5
done
echo "$act: $state"
```

```ts
const action = await rs.power.run("srv_123", { action: "reboot" });
const result = await rs.actions.wait(action.id);
if (result.state !== "done") console.error(result.state, result.reason);
```

```python
action = client.power.run("srv_123", action="reboot")
result = client.actions.wait(action.id)
if result.state != "done":
    print(result.state, result.reason)
```

```go
action, err := client.Power.Run(ctx, "srv_123", "reboot")
if err != nil {
	log.Fatal(err)
}
result, err := client.Actions.Wait(ctx, action.ID)
if err == nil && result.State != "done" {
	log.Println(result.State, *result.Reason)
}
```

```json
{
  "id": "act_42",
  "kind": "power",
  "server_id": "srv_123",
  "action": "reboot",
  "state": "done",
  "reason": null,
  "created": "2026-11-15T09:30:00Z",
  "started": "2026-11-15T09:30:04Z",
  "finished": "2026-11-15T09:30:07Z"
}
```

`done` means the command was carried out. A reboot then takes the usual time for the machine to come back; check your own service (SSH, HTTP) for that.

## One action per server

Only one action runs on a server at a time. While one is queued, running or not yet settled, another request for the same server answers `409 server_busy`, whatever key or Idempotency-Key you use. Wait for the first to finish.

The same answer comes while a server is being delivered or installed, or while a change started in the client area is under way.

## When the outcome is unknown

If the command was sent but no clear answer came back, the action ends `unknown`. We do not retry it silently, because a second power-off or reboot could do harm. RedSwitches staff check the machine and settle the action. Until then, further API power actions on that server answer `409 server_busy`. The power buttons in the client area keep working.

## Ceilings and the power-off brake

| Ceiling | Default | Answer |
|---|---|---|
| Actions per server | 6 in a rolling hour | `429 action_limit`, `details.per: "server_hour"` |
| Actions per account | 20 in a rolling hour | `429 action_limit`, `details.per: "account_hour"` |
| Power-offs per account | 5 within 10 minutes pauses API power | `403 power_paused` |

The power-off brake protects you from a runaway script. Four power-offs within 10 minutes go through; the fifth is refused, and API power for the whole account is paused and our staff are told. Every further power request answers `403 power_paused` until staff resume it. Contact support to resume.

Only accepted actions count towards the ceilings.

## Other refusals

| Answer | Meaning |
|---|---|
| `403 permission_denied` | The key lacks `power`, or the server is not on its list. |
| `409 server_not_active` | The server is pending, suspended or ended. A suspended server cannot be powered on through the API. |
| `422 not_supported` | This server cannot be powered through the API. Use the client area. |

## Queued too long

An action that has not started within 15 minutes expires: it ends `failed` with reason `expired`, and nothing is done. Send a new request (with a new Idempotency-Key) if you still want it.

## Retries

If the power request itself times out, send it again with the **same** Idempotency-Key and body: you get the same action back, and the server is powered only once. See [Idempotency and retries](idempotency.md). Use a new key only for a new intention, such as a second reboot later.

## Recipes

Reboot every server that has a label starting with `web-`, one at a time:

```bash
for id in $(curl -sS "https://api.redswitches.com/v1/servers?limit=100" \
    -H "Authorization: Bearer $REDSWITCHES_API_KEY" | jq -r '.data[] | select(.label // "" | startswith("web-")) | .id'); do
  curl -sS -X POST "https://api.redswitches.com/v1/servers/$id/power" \
    -H "Authorization: Bearer $REDSWITCHES_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{"action":"reboot"}'
  sleep 120   # let it come back before the next one
done
```

```ts
const { data } = await rs.servers.list({ limit: 100 });
for (const s of data.filter((x) => x.label?.startsWith("web-"))) {
  const a = await rs.power.run(s.id, { action: "reboot" });
  await rs.actions.wait(a.id);
}
```

```python
for s in client.servers.list(limit=100).data:
    if (s.label or "").startswith("web-"):
        a = client.power.run(s.id, action="reboot")
        client.actions.wait(a.id)
```

```go
page, _ := client.Servers.List(ctx, &redswitches.ListOptions{Limit: 100})
for _, s := range page.Data {
	if s.Label != nil && strings.HasPrefix(*s.Label, "web-") {
		a, err := client.Power.Run(ctx, s.ID, "reboot")
		if err == nil {
			client.Actions.Wait(ctx, a.ID)
		}
	}
}
```

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

Keep the 6-per-hour and 20-per-hour ceilings in mind for larger fleets.
