# Background jobs

> Original: https://api.6io.io/en/docs/jobs/ (HTML). All pages: https://api.6io.io/llms.txt

`POST /v1/jobs` starts an operation in the background. You get a `202` answer and a job ID at once, then ask for the state yourself and collect the finished result. It is handy when a request is long or you do not want to keep a connection open.

For now there is one operation, `chat.completions` (chat of the `op` and `vn` groups). Images and video through `fal` and `sixio` already work as tasks: see [fal models](https://api.6io.io/md/en/fal.md) and [Sixio models](https://api.6io.io/md/en/sixio.md).

## A full example

```python
import time
import requests

API = "https://api.6io.io/v1"
H = {"Authorization": "Bearer sixio_sk_..."}

job = requests.post(f"{API}/jobs", headers=H, json={
    "model": "op/openai/gpt-4o-mini",
    "operation": "chat.completions",
    "input": {"messages": [{"role": "user", "content": "Hello!"}], "max_tokens": 200},
}).json()

while True:
    state = requests.get(job["status_url"], headers=H).json()
    if state["status"] != "running":
        break
    time.sleep(2)

if state["status"] == "succeeded":
    result = requests.get(job["result_url"], headers=H).json()
    print(result["choices"][0]["message"]["content"])
else:
    print(state["status"], state.get("error"))
```

## Create a job

`input` is the body of an ordinary chat request without the `model` field (the model goes next to `operation`). Streaming (`stream`) is not supported in jobs: use `POST /v1/chat/completions` for it. Paid tools and non-text input are refused just as in ordinary chat. The `Idempotency-Key` header works as everywhere. To avoid polling the state, add `webhook_url`: when the job ends, we send a notification to that address ([webhooks](https://api.6io.io/md/en/webhooks.md)).

The answer is `202`, and the `Location` header points to the state:

```json
{
  "id": "req_8fK2xQ1mZpVwT9sJdL4nAg",
  "object": "job",
  "operation": "chat.completions",
  "model": "op/openai/gpt-4o-mini",
  "status": "running",
  "status_url": "https://api.6io.io/v1/jobs/req_8fK2xQ1mZpVwT9sJdL4nAg",
  "result_url": "https://api.6io.io/v1/jobs/req_8fK2xQ1mZpVwT9sJdL4nAg/result"
}
```

The job ID equals `X-Sixio-Request-Id`, so its money is visible in `GET /v1/requests/{id}` as well. If there is no free place, the answer comes at once (`429`): the job does not wait for one. While the service restarts, new jobs get `503` with `Retry-After`.

## State

`GET /v1/jobs/{id}` returns `status`:

| `status` | Meaning |
| --- | --- |
| `running` | the job is in progress |
| `succeeded` | done, the result can be collected |
| `failed` | not done, the reason is in `error`, no money is charged |
| `unknown` | the provider did not confirm the outcome (a dropped connection, overload): there is no result, the money is checked and closed later |

The `billing` field holds the state of the money and the charged amount in credits.

## The result

`GET /v1/jobs/{id}/result` returns an ordinary chat answer. Reading starts nothing and charges nothing.

- While the job is running, the answer is `202` with a link to the state and `Retry-After`.
- The result is kept for a limited time (a day by default). Collect it in that time: after that, `410 result_expired`.
- For `failed` and `unknown` the answer is `409` with the code `job_failed` or `job_outcome_unknown`.
- Sometimes the outcome was unknown and the check showed that the provider did the work. The job becomes `succeeded` and is paid for, but we have no answer: `409 result_unavailable`.
- An answer larger than 4 MB is also paid for but not stored: `409 result_too_large`. Send such requests as an ordinary `POST /v1/chat/completions`.
- If the result cannot be read for the moment, the answer is `503` with `Retry-After`: read again.

## Cancel

Jobs cannot be cancelled: a chat cannot be cancelled once it is sent to the provider, and closing the connection does not stop it.

## Money

As in ordinary chat: a reserve before the start, a charge by the actual cost after the answer.

- The provider refused because of your request: the reserve is returned.
- The provider refused because of the request rate (`429`) or could not be reached: no work was done, the reserve is returned, and the job ends with the error `upstream_busy`. You can send it again.
- The outcome is unknown (a timeout, `5xx`, a drop after sending): the reserve stays while we check the cost. A paid request is not sent again. The same happens to a job that did not finish within the provider's timeout, for example when the service restarts.

What to do on failures is in [Retries and failures](https://api.6io.io/md/en/retries.md).
