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

# Async Jobs

> Use async chat requests with job polling for long-running operations and background processing.

# Async Jobs

The sync endpoint (`POST /v1/chat`) waits for the AI to finish before responding. For long operations or background processing, use the async endpoint instead.

<Tip>
  **For batch processing where speed dominates**, consider `model_tier: "turbo"` in your request body — the fastest tier, optimised for high-volume workflows. For high-stakes documents in your batch, use `pro` or `max` instead. See [Model Selection](/guides/model-selection) for the full matrix.
</Tip>

## When to use async

| Use sync (`/v1/chat`)     | Use async (`/v1/chat/async`)  |
| ------------------------- | ----------------------------- |
| Quick edits and questions | Complex multi-step operations |
| Interactive chat UI       | Background processing         |
| Simple integrations       | SSE streaming for progress    |
|                           | HITL approval workflows       |

## Async flow

### 1. Start the job

```bash theme={null}
curl -X POST https://api.superdocs.app/v1/chat/async \
  -H "Authorization: Bearer sk_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Restructure this entire document into 5 sections",
    "session_id": "my-session",
    "document_html": "..."
  }'
```

Response:

```json theme={null}
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "session_id": "my-session",
  "status": "pending",
  "message": "Chat request queued for processing"
}
```

The `job_id` is an opaque UUID — treat it as a string, don't parse it. Chat jobs and attachment-processing jobs share the same id space and the same `GET /v1/jobs/{job_id}` path.

### 2. Poll for status

```bash theme={null}
curl https://api.superdocs.app/v1/jobs/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_YOUR_API_KEY"
```

### 3. Get the result

When `status` is `"completed"`, the `result` field contains the AI response and document changes:

```json theme={null}
{
  "job_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "result": {
    "response": "I've restructured the document into 5 sections...",
    "session_id": "my-session",
    "document_changes": {
      "updated_html": "<div data-chunk-id=\"...\">...</div>",
      "version_id": "...",
      "changes_summary": "Document updated by AI"
    },
    "usage": {
      "monthly_used": 44,
      "monthly_limit": 500,
      "monthly_remaining": 456,
      "was_billable": true,
      "subscription_tier": "free"
    }
  }
}
```

## Job statuses

| Status              | Meaning                                                           | What to do                                                                                                                                                                                                                |
| ------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pending`           | Queued, waiting to start                                          | Keep polling                                                                                                                                                                                                              |
| `in_progress`       | AI is processing                                                  | Keep polling                                                                                                                                                                                                              |
| `awaiting_approval` | The job is paused for your input — check `metadata.awaiting_kind` | **Change review** (HITL): read `metadata.pending_changes`, call `/approve`. **Continue prompt** (large edit): read `metadata.continue_prompt`, call `/continue` — see [Continuing a large edit](#continuing-a-large-edit) |
| `completed`         | Finished                                                          | Read `result`                                                                                                                                                                                                             |
| `failed`            | Error occurred                                                    | Read `error` field                                                                                                                                                                                                        |
| `cancelled`         | Cancelled by you                                                  | No action needed                                                                                                                                                                                                          |

<Note>
  **Revert vs. in-flight jobs.** [Revert](/concepts/sessions#revert-a-session-to-a-previous-message) returns `409` while a chat job on the same session is in `pending`, `in_progress`, or `awaiting_approval`. Wait for the job to land in `completed`/`failed`/`cancelled`, or cancel it explicitly via `POST /v1/jobs/{job_id}/cancel`, before retrying revert.
</Note>

## Continuing a large edit

A very large edit can be too big to finish in a single turn. When that happens the AI applies as much as it can, keeps that work, and pauses with `status: "awaiting_approval"` and `metadata.awaiting_kind: "continue_prompt"` — asking whether to continue with the rest. This is distinct from HITL change review (which instead sets `metadata.pending_changes`) and can occur in either approval mode.

The pause carries a `metadata.continue_prompt` object:

```json theme={null}
{
  "status": "awaiting_approval",
  "metadata": {
    "awaiting_kind": "continue_prompt",
    "continue_prompt": {
      "message": "I've updated 500 of 864 sections so far. 364 remain. Want me to continue with the rest?",
      "done": 500,
      "total": 864,
      "remaining": 364
    }
  }
}
```

Resume — or stop — by calling `/continue`:

```bash theme={null}
curl -X POST https://api.superdocs.app/v1/chat/{session_id}/continue \
  -H "Authorization: Bearer $SUPERDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"job_id": "550e8400-e29b-41d4-a716-446655440000", "continue": true}'
```

* `continue: true` — resume; the job returns to `in_progress` and finishes the rest.
* `continue: false` — stop here; everything applied so far is kept.

The job may pause again for the next segment, so handle `continue_prompt` in your polling loop just like repeated HITL rounds — keep going until `status` is `completed`. If you never respond, the job is cleaned up after 1 hour, like any other `awaiting_approval` job.

## Latency expectations and timeout strategy

Knowing how long an operation *should* take helps you decide when to surface a "still processing" hint and when to treat a job as broken.

### Typical latency by operation type

| Operation                                     | Expected duration | Surface "still processing" after | Treat as failed after   |
| --------------------------------------------- | ----------------- | -------------------------------- | ----------------------- |
| Single-section edit                           | \< 10 seconds     | 30 seconds                       | 2 minutes               |
| Multi-section edit (3–5 sections)             | 30–90 seconds     | 2 minutes                        | 5 minutes               |
| Full-document operation (10+ sections)        | 60–180 seconds    | 3 minutes                        | 10 minutes              |
| Complex operation (merge, restructure, batch) | 120–300 seconds   | 4 minutes                        | 15 minutes              |
| Very large document (hundreds of sections)    | 5–25 minutes      | 8 minutes                        | 30 minutes (server cap) |

These are medians for typical documents (5–20 sections). Larger documents, embedded images, or operations using `model_tier: "max"` with `thinking_depth: "deep"` may exceed these ranges. The `model_tier` and `thinking_depth` you choose materially affect latency — see [Model Selection](/guides/model-selection). The server enforces a hard 30-minute wall-clock cap on every chat turn (sync and async); requests that exceed it return a graceful `504` with a suggestion to split the work across smaller turns.

### Polling strategy that surfaces progress

```python theme={null} theme={null}
import time
import requests

MAX_WAIT = 300        # 5 minutes — adjust per operation type
WARN_AFTER = 60       # surface "still processing" after 60 seconds
POLL_INTERVAL = 2     # seconds between status checks

def wait_for_job(job_id: str, headers: dict) -> dict:
    start = time.time()
    last_warn = 0

    while True:
        r = requests.get(f"https://api.superdocs.app/v1/jobs/{job_id}", headers=headers)
        r.raise_for_status()
        job = r.json()
        elapsed = time.time() - start

        if job["status"] == "completed":
            return job["result"]
        if job["status"] == "failed":
            raise RuntimeError(job.get("error", "Unknown error"))
        if job["status"] == "cancelled":
            raise RuntimeError("Job was cancelled")

        if elapsed > MAX_WAIT:
            raise TimeoutError(f"Job did not complete within {MAX_WAIT}s")

        # Surface a "still processing" hint to the user every 30s after WARN_AFTER
        if elapsed > WARN_AFTER and elapsed - last_warn >= 30:
            print(f"  Still processing... ({int(elapsed)}s elapsed)")
            last_warn = elapsed

        time.sleep(POLL_INTERVAL)
```

For real-time progress (instead of polling silence), open the SSE stream in parallel and surface every `intermediate` event to the user — see [Streaming → Rendering intermediate events](/guides/streaming#rendering-intermediate-events-in-your-ui).

### Job retention

All jobs — pending, in-progress, awaiting-approval, or terminal — are automatically removed 1 hour after creation. For user-facing workflows that may run longer (or where the user steps away), keep `MAX_WAIT` well under 1 hour and persist the `job_id` so you can resume polling later.

## Job types

The `job_type` field on every job row tells you which lifecycle to expect.

| `job_type`              | Triggered by                              | Lifecycle                                                                                                                                                                                                                                                                                                            |
| ----------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chat`                  | `POST /v1/chat/async`                     | Standard chat turn. Supports `awaiting_approval` for HITL. SSE stream available at `/v1/chat/{session_id}/stream?job_id=...`.                                                                                                                                                                                        |
| `attachment_processing` | Background attachment ingestion           | Internal; usually completes in seconds.                                                                                                                                                                                                                                                                              |
| `large_export`          | `POST /v1/documents/export/email-request` | Background export of an oversize document. No SSE; poll `/v1/jobs/{job_id}`. On `completed`, `result.download_url` carries a 7-day signed link and an email has been sent to the recipient. 24-hour SLA. See [Email fallback for very large documents](/concepts/documents#email-fallback-for-very-large-documents). |

`large_export` jobs are fire-and-forget — the recipient receives an email when the render finishes, so polling is optional. Use it when you want to surface progress in your UI rather than letting the user check their inbox.

## Cancel a job

Only `pending` and `in_progress` jobs can be cancelled:

```bash theme={null}
curl -X POST https://api.superdocs.app/v1/jobs/550e8400-e29b-41d4-a716-446655440000/cancel \
  -H "Authorization: Bearer sk_YOUR_API_KEY"
```

## List all jobs

```bash theme={null}
curl https://api.superdocs.app/v1/jobs \
  -H "Authorization: Bearer sk_YOUR_API_KEY"
```

## Polling example

```python theme={null}
import time
import requests

API_KEY = "sk_YOUR_API_KEY"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

# Start async job
response = requests.post("https://api.superdocs.app/v1/chat/async",
    headers={**HEADERS, "Content-Type": "application/json"},
    json={"message": "Summarize this document", "session_id": "my-session", "document_html": "..."}
)
job_id = response.json()["job_id"]

# Poll until complete
while True:
    job = requests.get(f"https://api.superdocs.app/v1/jobs/{job_id}", headers=HEADERS).json()

    if job["status"] == "completed":
        print("AI response:", job["result"]["response"])
        break
    elif job["status"] == "failed":
        print("Error:", job["error"])
        break
    elif job["status"] == "awaiting_approval":
        meta = job.get("metadata", {})
        if meta.get("awaiting_kind") == "continue_prompt":
            # Large edit paused mid-way — resume it (send continue=false to stop instead).
            print(meta["continue_prompt"]["message"])
            requests.post("https://api.superdocs.app/v1/chat/my-session/continue",
                headers={**HEADERS, "Content-Type": "application/json"},
                json={"job_id": job_id, "continue": True})
            # keep polling — the job returns to in_progress and may pause again
        else:
            # HITL change review — inspect the changes, then call /approve.
            print("Changes need approval:", meta.get("pending_changes"))
            break

    time.sleep(2)
```

<Note>
  Jobs are automatically cleaned up 1 hour after completion, failure, or cancellation. Jobs in `awaiting_approval` status are also cleaned up after 1 hour — approve or deny changes within this window.
</Note>
