# Long-running operations

Some work cannot finish inside a request: provisioning a site builds a database, buckets,
routing and seeded content; a search reindex walks everything the site has. Rather than
answer `202` and leave you guessing, those calls name an **Operation** — a resource you
can poll, and the only honest answer to "is it done yet".

```sh title="quickstart.sh"
state='{"done": false}'
until [ "$(jq -r '.done' <<<"$state")" = true ]; do
  sleep 2
  state=$(curl_json "$SITE_API/site-api/v1/operations/$signup_token")
  jq -r '"  " + (.metadata.step // "?")' <<<"$state" >&2
done

# Done means finished, not succeeded: exactly one of `response` and `error` is there.
jq -e '.error | not' <<<"$state" >/dev/null || { jq '.error' <<<"$state" >&2; exit 1; }
SITE_URL=$(jq -r '.response.website_url' <<<"$state")
```

Three habits make polling one of these correct:

- **`done` means finished, not succeeded.** Exactly one of `response` and `error` is
  present once it flips, and the error is the ordinary [Status](/concepts/errors) shape.
- **Read `metadata.step`, not a percentage.** The step is the named hop the work is on;
  where nothing counts anything, no number is reported rather than a made-up one.
- **Cancelling is a request, not an event.** `:cancel` records it and returns the
  operation still running; the work stops at its next checkpoint, if it has one left, and
  you learn the outcome from `done` — a `CANCELLED` error means it stopped, success means
  it finished anyway.

## 17. Long-running operations (AIP-151)

Anything that returns before the work is done MUST return an **Operation**, not
a bare `202` and a hope.

```json
{
  "name": "websites/6a5e.../operations/64ff...",
  "done": false,
  "metadata": { "type": "PROVISION_WEBSITE", "step": "SEEDING_CONTENT", "progress_percent": 40 }
}
```

When `done` is `true`, exactly one of `response` (the resulting resource) or
`error` (a `Status` body, [§13](/concepts/errors)) is present.

- `GET {operation_name}` polls. `POST {operation_name}:cancel` requests
  cancellation; the operation is not cancelled until `done` is `true` with a
  `CANCELLED` error.
- The initiating call returns `202 Accepted` with the Operation and a
  `Location` header pointing at it.
- Operations are retained at least 24 h after completion.

**An Operation is server-minted.** It has Get and `:cancel` and nothing else: a
client cannot create one, and a List of every operation a resource has ever run
is an audit log, which is a different resource. It carries no `etag` either —
[§14](/concepts/concurrency) wants one where there are concurrent editors, and this resource has no
editors: `:cancel` raises a flag, it does not submit a version.

**Cancellation is a request, honoured at a checkpoint.** `:cancel` records it
and returns the operation, still running. A worker consults the flag where it
can still stop; past its last checkpoint the work finishes and the operation
reports success — which the `done`/`CANCELLED` rule above makes visible rather
than letting the caller assume the cancel took. `:cancel` on an already finished
operation is `FAILED_PRECONDITION`: finished work cannot be un-finished.

**`metadata` is service-specific and honest.** Emit `step` — the named hop the
work is on — rather than a `progress_percent` nothing measures. `progress_percent`
belongs in the example above and in a service that genuinely counts something.

**A kick-off that also creates an addressable resource keeps its `201`.** §17
covers calls whose whole result is asynchronous. Where the resource exists by the
time the response is written — a website whose `provisioning_status` is `PENDING`,
a user whose site is still being built — the answer stays `201` + `Location` +
the resource, plus an `operation` field naming what to poll. Downgrading that to
`202` would take the resource's name away from the caller and hand back only a
receipt for work.

**And there is no `operation` field when the resource is what you poll.** A
resource created to *be* one run of work — `ai_executions` is the platform's one
— already carries the whole lifecycle an Operation would: a `state` that ends,
an `error` when it ends badly, a `:cancel` that raises the same flag honoured at
the same kind of checkpoint, and, unlike an Operation, a List that is its history.
Minting an Operation beside it would publish two names for one run and two places
to read whether it finished.
