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

# Long-running operations

> How reviews, redlines, translations and reports run past the 29-second request limit, and how to wait for them.

> [!NOTE]
>
> **Preview.** Operations are available to organisations in the Wordsmith MCP and files pilot. See the [preview overview](/preview). This page describes how they work as built.

Every API request must finish within **29 seconds**. A playbook review takes minutes. A report over 200 documents can take much longer. So every endpoint that can outlast a request starts the work in the background and returns an **operation** straight away.

An operation is a handle with a status. You can poll it, long-poll it, or receive a callback when it finishes. The same pattern covers every long-running endpoint in the API and every long-running tool in the MCP server.

## The operation object

```json theme={null}
{
  "id": "3b8e0f1c-6a2d-4e57-9c1b-2f7d8a4e6b90",
  "type": "review",
  "status": "running",
  "progress": { "stage": "checks", "completed": 14, "total": 32 },
  "resource": { "type": "review", "id": "5f0c9a12-8e3b-4d71-a6c4-0b2e9f7d3c58" },
  "result": null,
  "error": null,
  "poll_after_seconds": 5,
  "created_at": "2026-10-13T09:00:00Z",
  "updated_at": "2026-10-13T09:01:12Z",
  "completed_at": null
}
```

* `status` moves from `queued` to `running`, then to one of `succeeded`, `failed` or `cancelled`.
* `resource` names the object the operation works on. Read it for the full picture, for example `GET /reviews/{id}`. `resource.id` is `null` until the work has created its resource, such as the session a draft runs in.
* `progress` is set while the work runs, and a finished operation keeps its last `progress`.
* `result` is set on success. For a review it holds finding counts and the redline `file_id`. For a document action it holds the new `file_ids`. [Get Operation](/api-reference/operations/get-operation) lists the result of every type. Numbers in `result` are JSON numbers.
* `error` is set on failure, with a `code`, a `message` and `retryable`.

```mermaid theme={null}
stateDiagram-v2
    [*] --> queued
    queued --> running
    running --> succeeded
    running --> failed
    queued --> cancelled
    running --> cancelled
```

## Three ways to wait

**Long-poll (recommended for scripts)**

Call [Get Operation](/api-reference/operations/get-operation) with `wait=20`. The request holds for up to 20 seconds and returns the moment the operation finishes.

```python theme={null}
import requests

API = "https://api.wordsmith.ai/api/v1"
HEADERS = {"Authorization": "Bearer sk-ws-api1-your_api_key_here"}

def wait_for(operation_id: str) -> dict:
    while True:
        op = requests.get(f"{API}/operations/{operation_id}", params={"wait": 20}, headers=HEADERS).json()
        if op["status"] in ("succeeded", "failed", "cancelled"):
            return op
```

A three-minute review costs about nine requests this way.

**Callback (recommended for servers)**

Pass a `callback_url` when you start the operation. Wordsmith sends it a signed `POST` when the operation finishes, and retries for 24 hours if your server does not answer. Nothing polls. See [Callbacks](/proposed/callbacks). Callbacks need an API key; OAuth callers long-poll.

**Poll**

Call [Get Operation](/api-reference/operations/get-operation) without `wait`. It returns immediately. Wait `poll_after_seconds` before asking again.

### Getting the result in one call

Every endpoint that starts an operation also takes `wait`, from `0` to `20` seconds. Quick work, such as comparing two short files or filling a blueprint from your own values, then comes back finished in the same response:

```bash theme={null}
curl -X POST https://api.wordsmith.ai/api/v1/files/compare \
  -H "Authorization: Bearer sk-ws-api1-your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"original_file_id": "9a1d7e44-2b6c-4f08-8d3a-71c5e0b9f2a6",
       "revised_file_id": "e3c58b21-7f04-4a96-b1d2-6c9e0a4f8b73", "wait": 20}'
```

If the work is not finished when `wait` runs out, you get the operation as usual and carry on from there. Code written for the slow path always works.

## Retrying safely with `Idempotency-Key`

If your request times out, you cannot tell whether the review started. Send an `Idempotency-Key` header on every request that starts an operation:

```bash theme={null}
-H "Idempotency-Key: 6f1d2c3e-review-nda-42"
```

* The first request with a key starts the operation.
* A repeat with the same key and body within 30 days returns the same operation with `200`. Nothing runs twice, and you are not charged twice.
* If the key's operation failed before its work started, with `error.code` `request_failed` and `retryable` `true`, a repeat starts the work in a new operation with a new id, and the key then returns that one. The failed operation keeps its id, its outcome and its callback.
* A repeat with the same key and a different body returns `409 idempotency_key_reused`.

Use a fresh key for each piece of work, such as a UUID you store next to the job in your own system.

## When an operation fails

`error.retryable` says whether the same request can succeed on a new attempt. A retryable `request_failed` operation never started, so a repeat with the same `Idempotency-Key` starts the work in a new operation. For any other failure, start the work again with a new key.

| `error.code` | Meaning |
| - | - |
| `request_failed` | The request failed while it started the work. `retryable` is `true` when nothing ran. When it is `false`, the input was refused, or the work had started and may still be running: check the operation's `resource` before you start it again with a new key. |
| `worker_lost` | The worker running the job stopped responding. `retryable` is `true`. |
| `question_failed`, `review_failed`, `redline_failed`, `action_failed`, `draft_failed`, `export_failed`, `generation_failed` | The work of that type failed part-way. `retryable` is `true`. |
| `invalid_document` | An input file could not be read: damaged, password-protected, or not a valid DOCX or PDF. |
| `no_amendments` | A redline found nothing to change in the document. |
| `report_too_large` | The report is too large to export. |
| `review_deleted`, `repository_deleted` | The resource was deleted while the work ran. |

## Cancelling

[Cancel Operation](/api-reference/operations/cancel-operation) asks the worker to stop. Cancellation is best-effort: the operation may read `running` for a few seconds before it turns `cancelled`. Work already done, such as cells computed in a report, is kept and charged.

## Limits

| Limit | Value |
| - | - |
| `wait` | 20 seconds per request |
| Long-polls waiting at once | 20 per organisation. Past that, Get Operation answers at once; ask again after `poll_after_seconds`. |
| Idempotency keys remembered | 30 days from the first request |
| Operations readable | 30 days from when they start |

Each endpoint that starts an operation keeps its own limits, such as the [request limit](/rate-limits) and credits. A long-poll counts once against the request limit, however long it holds.

## Which endpoints return an operation

| Endpoint | Operation `type` | Typical duration |
| - | - | - |
| [Create Question](/api-reference/agents/create-question) | `question` | Seconds to minutes |
| [Create Review](/api-reference/reviews/create-review) | `review` | 1–15 minutes |
| [Generate Redline](/api-reference/reviews/generate-redline) | `review_redline` | 30 seconds – 5 minutes |
| [Document actions](/api-reference/documents/compare) | `document_action` | Seconds to a few minutes |
| [Add Documents](/api-reference/repositories/add-documents) | `repository_indexing` | Seconds per document |
| [Run Report](/api-reference/reports/run-report) | `report_run` | Minutes, growing with rows × columns |
| [Export Report](/api-reference/reports/export-report) | `report_export` | Under a minute |
| [Generate Playbook](/api-reference/playbooks/generate-playbook) | `playbook_generation` | Several minutes |
| [Create Draft](/api-reference/drafts/create-draft) | `draft` | Seconds from a blueprint with every value; up to a few minutes otherwise |
| [Submit Decisions](/proposed/api/reviews/submit-decisions) (proposed) | `review_decisions` | 30 seconds – 5 minutes |

The proposal also planned a research report (`research_report`). It was not built: for a researched memo, ask an agent with [Create Question](/api-reference/agents/create-question).

Questions keep their existing status endpoint, and each one also gets an operation, so the same waiting code works for them.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.