Skip to main content

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.
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 for the full matrix.

When to use async

Use sync (/v1/chat)Use async (/v1/chat/async)
Quick edits and questionsComplex multi-step operations
Interactive chat UIBackground processing
Simple integrationsSSE streaming for progress
HITL approval workflows

Async flow

1. Start the job

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:
{
  "job_id": "job_abc123",
  "session_id": "my-session",
  "status": "pending",
  "message": "Chat request queued for processing"
}

2. Poll for status

curl https://api.superdocs.app/v1/jobs/job_abc123 \
  -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:
{
  "job_id": "job_abc123",
  "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

StatusMeaningWhat to do
pendingQueued, waiting to startKeep polling
in_progressAI is processingKeep polling
awaiting_approvalHITL mode — changes need your reviewCheck metadata.pending_changes, then call /approve
completedFinishedRead result
failedError occurredRead error field
cancelledCancelled by youNo action needed

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

OperationExpected durationSurface “still processing” afterTreat as failed after
Single-section edit< 10 seconds30 seconds2 minutes
Multi-section edit (3–5 sections)30–90 seconds2 minutes5 minutes
Full-document operation (10+ sections)60–180 seconds3 minutes10 minutes
Complex operation (merge, restructure, batch)120–300 seconds4 minutes15 minutes
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.

Polling strategy that surfaces progress

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.

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.

Cancel a job

Only pending and in_progress jobs can be cancelled:
curl -X POST https://api.superdocs.app/v1/jobs/job_abc123/cancel \
  -H "Authorization: Bearer sk_YOUR_API_KEY"

List all jobs

curl https://api.superdocs.app/v1/jobs \
  -H "Authorization: Bearer sk_YOUR_API_KEY"

Polling example

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":
        print("Changes need approval:", job["metadata"]["pending_changes"])
        break

    time.sleep(2)
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.