# Operations and retries

Source: https://heaven.dev/docs/operations

Changes are saved first and finish shortly after. Follow a change's operation, and retry safely with an Idempotency-Key.

## Operations

When you create, start, stop, rename or snapshot a Computer, Heaven saves the change, and the Computer's server carries it out shortly after. The response is an operation you can follow. Closing the connection never cancels it.

| State | Means |
| --- | --- |
| `accepted` | Heaven saved it, and the server is working on it. |
| `complete` | It's done. |
| `failed` | It couldn't be done. `error_code` says why, and `message` says what to do next. |
| `superseded` | A newer change replaced it before it finished, such as a stop sent during a restart. |

`accepted` doesn't mean the Computer is ready. It's ready when its own `state` is `running`.

## Read an operation

`GET /v1/operation/`

Returns an operation's progress. With `wait`, it returns the outcome once the operation finishes or the wait ends.

You can read any operation you started, even after its Computer is renamed or deleted. If the wait ends before the operation does, the response is `unknown` with the operation's ID. That means it hasn't finished yet, not that it failed, so ask again.

#### Query

- `operation` string *required*: The operation's ID, as the change returned it.
- `wait` boolean: Wait until it finishes, up to `timeout`.
- `timeout` string: How long `wait` waits: 10s by default, up to 10m.

- An API key needs `operation view` in its commands.

Request:

```sh
curl 'https://heaven.dev/v1/operation/?operation=8540e6f2-9f39-4d39-89b5-eeac80d0ac97&wait=true' \
  -H "Authorization: Bearer $HEAVEN_API_KEY"
```

200:

```json
{
  "version": 1,
  "data": {
    "operation": {
      "id": "8540e6f2-9f39-4d39-89b5-eeac80d0ac97",
      "kind": "create",
      "computer_id": "0098166a-9908-43ab-967b-4111c45c3b33",
      "revision": 1,
      "state": "complete",
      "created_at": "2026-11-01T23:19:37Z",
      "completed_at": "2026-11-01T23:20:11Z"
    }
  }
}
```

503 Still running:

```json
{
  "version": 1,
  "error": {
    "kind": "unknown",
    "message": "The operation is still in progress. Check again with `operation view 8540e6f2-…`.",
    "operation_id": "8540e6f2-9f39-4d39-89b5-eeac80d0ac97"
  }
}
```

## Operation groups

Some changes act on several Computers at once: a [pool snapshot](https://heaven.dev/docs/pools#pool-snapshots), a pool [split or combine](https://heaven.dev/docs/pools#size-and-shape), a [pool copy](https://heaven.dev/docs/pools#create), and [deleting your account](https://heaven.dev/docs/account#delete). Each returns one operation group that holds each Computer's operation. Read it with the same `GET /v1/operation/`, and `wait` works too.

A group's `state` is `accepted` until every operation in it finishes. Then it's `complete`, or `failed` if any operation failed or was superseded.

200:

```json
{
  "version": 1,
  "data": {
    "operation": {
      "id": "4baa2b68-1bd1-4cd2-b965-c9eb9513de73",
      "kind": "pool snapshot create",
      "state": "accepted",
      "pool_id": "pool-a84a2847cd584a0c",
      "subject": "nightly",
      "operations": [
        {
          "id": "66778689-c39f-403e-a841-a8c445fd5bf0",
          "kind": "snapshot",
          "computer_id": "317ff25b-84a8-4112-815e-d79f0ff6d5b0",
          "revision": 2,
          "state": "accepted",
          "created_at": "2026-11-02T13:15:16Z"
        },
        …
      ],
      "created_at": "2026-11-02T13:15:16Z"
    }
  }
}
```

## Streaming a change

Send a change with `Accept: application/x-ndjson` to get its progress as it happens, one JSON object per line. The first line is `accepted`. An `operation` line follows as each operation finishes, and `complete` comes last. If 25 seconds pass before then, the last line is `pending`, with the operations to read later.

A change that names several Computers in `computers` (except a delete), or a change to a whole pool, returns its usual JSON instead.

Request:

```sh
curl https://heaven.dev/v1/computer/modify \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  -H "Accept: application/x-ndjson" \
  --json '{"computer":"my-site","action":"restart"}'
```

200 application/x-ndjson:

```json
{"version":1,"event":"accepted","data":{"request_id":"…","operation":{"id":"7bbd53b7-…","kind":"restart","state":"accepted"}}}
{"version":1,"event":"operation","data":{"operation":{"id":"7bbd53b7-…","kind":"restart","state":"complete"}}}
{"version":1,"event":"complete","data":{"operations":[{"id":"7bbd53b7-…","state":"complete"}]}}
```

## Retrying safely

A network error can lose the response after the change was saved. To retry such a change safely, send an `Idempotency-Key` header: any string of 1 to 128 bytes that you choose. It matters most for a change that creates something or spends money. Sending the same request again with the same key returns the first response, and never makes a second Computer, operation or purchase. The [`heaven` program](https://heaven.dev/docs/cli) sends one for you.

Request:

```sh
curl https://heaven.dev/v1/computer/create \
  -H "Authorization: Bearer $HEAVEN_API_KEY" \
  -H "Idempotency-Key: create-my-site" \
  --json '{"name":"my-site","pool":"web"}'
```

- **Choose the key before the first attempt**, and keep it with the request. Heaven makes one when you leave it out, but you can't recover that one from a lost response.
- **The same key with a different request** is refused (`conflict`), so a typo never acts on the wrong thing.
- **Idempotency keys belong to you**, not to an API key. A retry with another of your API keys returns the same response.

| Retry | Commands |
| --- | --- |
| With `Idempotency-Key` | `create`, `delete`, `rename`, `move`, `resize`, `reset`, `tag`, `comment`, `start`, `stop`, `restart`, `pause`, `resume`, every `snapshot` change, `pool create`, `pool split`, `pool combine`, `pool snapshot create`, `restore` and `delete`, `capacity buy`, `billing charge`, `ip buy`, `feedback` |
| Send it again | Everything else that changes something. It sets the same state again, or returns `conflict` or `not_found` because it's already done. |
| Check first | `api-key create` and `share link add` make a second key or link. `organization create`, `organization team create` and `integration create` return `conflict` because the name is taken. |
| Never repeated | `exec` with a command. After a lost response the outcome is `unknown`, so check inside the Computer. |

A command that doesn't take an `Idempotency-Key` refuses one (`invalid`) rather than ignoring it.

## When the outcome is unknown

An `unknown` error (HTTP 503) means Heaven couldn't confirm what happened: the change may or may not have been saved. It never means the change failed. To find out, read the operation if the error names one, or send the same request with the same `Idempotency-Key`. Either tells you what happened without doing it twice.
