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 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
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
| Status | Meaning | What to do |
|---|
pending | Queued, waiting to start | Keep polling |
in_progress | AI is processing | Keep polling |
awaiting_approval | HITL mode — changes need your review | Check metadata.pending_changes, then call /approve |
completed | Finished | Read result |
failed | Error occurred | Read error field |
cancelled | Cancelled by you | No 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
| 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 |
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.