> ## Documentation Index
> Fetch the complete documentation index at: https://docs.querying.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Async tasks

> The task lifecycle, statuses, priority, and retention.

There is no synchronous endpoint. Driving an AI engine takes seconds to minutes — often
in a real browser — so every request becomes a durable queued task that you collect later.

## Lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> QUEUED: POST /v1/async/task
    QUEUED --> PROCESSING: worker claims
    PROCESSING --> COMPLETED: engine answered
    PROCESSING --> FAILED: retries exhausted
    PROCESSING --> QUEUED: transient error, requeued
    COMPLETED --> [*]: swept after 24h
    FAILED --> [*]: swept after 24h
```

## Statuses

<ResponseField name="QUEUED" type="status">
  Accepted and durable. Waiting for a worker.
</ResponseField>

<ResponseField name="PROCESSING" type="status">
  A worker holds a lease on the task and is driving the engine.
</ResponseField>

<ResponseField name="COMPLETED" type="status">
  Terminal. The `response` field is populated.
</ResponseField>

<ResponseField name="FAILED" type="status">
  Terminal. The `error` field carries the reason.
</ResponseField>

<Note>
  Internally the queue stores `RUNNING`, but the wire contract exposes `PROCESSING`. This
  is deliberate — the database enum is internal state, the status you see is the contract.
</Note>

A task that hits a transient failure (engine timeout, no account available) returns to
`QUEUED` and is retried. Only once retries are exhausted does it settle in `FAILED`, so
`PROCESSING → QUEUED` transitions are normal and not something to alarm on.

## Priority

`priority` accepts `1` through `10`, higher wins. Values outside that range are clamped
rather than rejected. Defaults to `1`.

```json theme={null}
{ "taskType": "GEMINI", "priority": 8, "payload": { "prompt": "..." } }
```

Priority orders the queue; it does not reserve capacity. A high-priority task still waits
behind whatever is already `PROCESSING`, and it cannot conjure an account or IP that the
engine's pool does not have.

## Retention

Terminal tasks are retained for **24 hours**, then swept. After that,
`GET /v1/async/task/{id}` returns `404 NOT_FOUND` — the same response as an id that never
existed, because the row is gone and the two cases are indistinguishable.

If you rely on polling rather than webhooks, collect results well inside that window.

## Choosing webhook or polling

Prefer webhooks. Polling is the recovery path.

|                          | Webhook                        | Polling                   |
| ------------------------ | ------------------------------ | ------------------------- |
| Latency to result        | Immediate on completion        | Your poll interval        |
| Load on the API          | One POST per task              | One GET per poll per task |
| Requires public endpoint | Yes                            | No                        |
| Survives your downtime   | Retried \~30 min, then dropped | Yes, for 24h              |

A robust client does both: take the webhook as the fast path, and reconcile anything you
never heard about by polling before the retention window closes.
