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

# Jobs

> Lifecycle, polling, results, and idempotency

Everything returns a job. `POST /v1/text` and `POST /v1/audio` always respond with the same object (`{ id, status, error?, result? }`) regardless of input size:

* **Small inline text (≤ 50 KB, no `webhookUrl`)** is processed inline: HTTP **200**, the job arrives already `completed` with `result` populated, and it appears in your job history like any other.
* **Everything else** is queued: HTTP **202 Accepted** with `{ id, status: "created" }`, and the job finishes asynchronously.

Client code needs one path: check `status`. If it isn't `completed` or `failed`, fetch `GET /v1/jobs/:id` or wait for a [webhook](/concepts/webhooks).

## Lifecycle

```
created → pending → processing → completed
                                → failed
```

| Status       | Meaning                                                        |
| ------------ | -------------------------------------------------------------- |
| `created`    | Job accepted and enqueued                                      |
| `processing` | A worker is running the operation pipeline                     |
| `pending`    | A processing attempt failed; the job is waiting to be retried  |
| `completed`  | Done, `result` is populated                                    |
| `failed`     | All attempts exhausted, `error` is populated, credits refunded |

Processing is retried up to 3 times with exponential backoff before a job is marked `failed`. Fast-path text jobs skip the queue entirely and are born `completed`.

## Consuming results

Three ways to get a result, in order of preference:

| Mode      | How                                            | Use when                                                                                                                                                                                                                                                                   |
| --------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Immediate | Read `result` straight off the `POST` response | Inline text ≤ 50 KB, nothing else to do                                                                                                                                                                                                                                    |
| Long-poll | `GET /v1/jobs/:id?wait=25`                     | Everything else: the server holds the response until the job finishes or the timeout passes, so a typical job takes 1–2 calls instead of a polling loop. Each request counts against your rate limit, so `wait` saves quota too. See [Get a job](/api-reference/jobs-get). |
| Webhooks  | Pass `webhookUrl` at creation                  | High volume or long-running jobs where you don't want an open connection; see [Webhooks](/concepts/webhooks)                                                                                                                                                               |

## Results

`GET /v1/jobs/:id` returns:

```json theme={null}
{
  "success": true,
  "data": {
    "id": "665f1d00b8d4e91a2c3d4e60",
    "status": "completed",
    "result": {
      "outputUrl": "https://...signed-url...",
      "metrics": {
        "inputSize": 52428800,
        "outputSize": 6291456,
        "compressionRatio": 8.33,
        "operationsApplied": ["trim-silence", "normalize", "compress", "encode"]
      }
    }
  }
}
```

* **`outputUrl`**: file outputs. A fresh signed URL is generated on every read and is valid for 1 hour; just re-fetch the job to get a new one. Audio outputs are `.ogg` (Opus) by default, or `.mp3` if you opted in via the `encode` operation. Text jobs created from a `fileId` output a `.txt` file.
* **`outputText`**: inline output, returned for text jobs whose input was inline `text`.
* **Retention**: results are available for **7 days** after completion.

## Listing jobs

`GET /v1/jobs` returns newest-first, cursor-paginated:

```bash theme={null}
curl -s "https://api.robinzip.app/v1/jobs?type=audio&status=completed&limit=20" \
  -H "Authorization: Bearer $ROBIN_KEY"
```

```json theme={null}
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "665f1d00b8d4e91a2c3d4e60",
        "type": "audio",
        "status": "completed",
        "name": "episode.mp3",
        "metrics": { "compressionRatio": 8.33 },
        "createdAt": "2026-07-11T14:02:11.000Z",
        "completedAt": "2026-07-11T14:03:40.000Z"
      }
    ],
    "nextCursor": null
  }
}
```

Pass `nextCursor` back as `cursor` to fetch the next page. `limit` defaults to 20, max 100. See [List jobs](/api-reference/jobs-list) for all filters.

## Idempotency

`POST /v1/text`, `POST /v1/audio`, and `POST /v1/image` accept an optional `Idempotency-Key` header (1–255 characters, scoped to your account):

```bash theme={null}
curl -s https://api.robinzip.app/v1/audio \
  -H "Authorization: Bearer $ROBIN_KEY" \
  -H "Idempotency-Key: episode-42" \
  -H "Content-Type: application/json" \
  -d '{"audioId": "...", "preset": "podcast"}'
```

If a job with that key already exists, the existing job is returned instead of creating (and charging) a new one. Safe to use on retries after network failures.

<Note>
  Idempotency applies to queued jobs. Fast-path text jobs come back already completed and are not deduplicated.
</Note>
