HyperbotsHyperAPI/Docs
Reference

Async Mode

Async mode lets the API return immediately with a job_id instead of holding the HTTP connection open for the full inference run. Your client polls a status endpoint until the job completes. Every long-running endpoint — /v1/parse, /v1/extract, /v1/classify, /v1/split — supports it.

Why use async

Extraction on a multi-page document can take 130–160 seconds. CloudFront, like most CDNs, drops idle connections at 60 seconds. A synchronous call to a slow endpoint can succeed inside our infrastructure and still hand your client a connection-reset error at the edge. Async mode sidesteps this: each individual HTTP request stays sub-second, so the call survives any CDN, proxy, or load-balancer timeout in the path between you and the API.

Use async whenever you can't guarantee the calling context (browser, mobile, serverless function with hard wall-clock limits) will hold a connection for ~3 minutes. For batch workers running inside your own infrastructure, synchronous is fine.

How to enable

Add the header X-Async: true to any inference request. The request body and query parameters are unchanged from the synchronous call.

curl -X POST "https://apis.hyperbots.com/v1/parse?ocr_engine=paddle" \
  -H "X-API-Key: hk_live_your_key_here" \
  -H "X-Async: true" \
  -F "document_key=02b95e79-bc17-4e71-be99-fd4338ba00bf"

The response is a small job receipt, returned within ~100 ms:

{
  "job_id": "9b330094-505d-480c-812c-86a698a3ff02",
  "status": "pending",
  "poll_url": "/v1/jobs/9b330094-505d-480c-812c-86a698a3ff02"
}

Polling the job

GET the poll_url from the submit response (or build it yourself from the job_id: /v1/jobs/{job_id}). Poll every 3 seconds as a sane default; longer is fine, shorter is wasted load.

curl "https://apis.hyperbots.com/v1/jobs/9b330094-505d-480c-812c-86a698a3ff02" \
  -H "X-API-Key: hk_live_your_key_here"

Job status values

StatusMeaning
pending
The job has been accepted and is queued or running on a worker. Continue polling.
completed
The job finished successfully. The inference response is wrapped inside result (see shape below).
failed
The job ran but raised an error. error is a plain string carrying the HTTP-style status code and human-readable reason. result is null.
cancelled
You called DELETE /v1/jobs/{job_id} on the job. Surfaced only on GET /v1/jobs/recent — a direct GET /v1/jobs/{job_id} on a cancelled job returns status: failed.

A typical pending poll response:

{
  "job_id": "9b330094-505d-480c-812c-86a698a3ff02",
  "status": "pending"
}

On completed the response wraps the synchronous inference response inside its own result field. Job-level metadata ( created_at, completed_at, org_id, filename, endpoint) is at the top level. The OCR/extraction payload is at result.result (the outer field is the job envelope; the inner field is what a synchronous call would have returned directly):

{
  "job_id": "9b330094-505d-480c-812c-86a698a3ff02",
  "status": "completed",
  "request_id": "b91eaa9b-c75d-402d-a8dc-cb3af247c267",
  "org_id": "f9030855-340d-4cfd-b7e7-1f7b39f4ba42",
  "endpoint": "/v1/parse",
  "filename": "contract.pdf",
  "created_at": 1779447738.16,
  "completed_at": 1779447738.85,
  "error": null,
  "result": {
    "status": "success",
    "request_id": "b91eaa9b-c75d-402d-a8dc-cb3af247c267",
    "task": "parse",
    "model_used": "HyperLM",
    "result": {
      "ocr": "INVOICE\nAcme Corp ...",
      "pages": [{ "page_number": 1, "text": "INVOICE\nAcme Corp ..." }]
    },
    "duration_ms": 657,
    "metadata": {
      "pages": 1,
      "source": "document_key",
      "filename": "contract.pdf",
      "file_size": 5821
    }
  }
}

On failed the result field is null and error is a string carrying the HTTP-style status code and a human-readable reason:

{
  "job_id": "b891d13b-e068-4210-a1d3-3ca013f5a715",
  "status": "failed",
  "request_id": "a7f215b8-3339-4a83-9f79-03372b9e9eb4",
  "org_id": "f9030855-340d-4cfd-b7e7-1f7b39f4ba42",
  "endpoint": "/v1/parse",
  "filename": "notpdf.txt",
  "created_at": 1779447744.92,
  "completed_at": 1779447744.96,
  "result": null,
  "error": "415: Unsupported file type: text/plain. Supported: PDF, images."
}

The same conditions that would have raised a typed error on a synchronous call (415 for unsupported file types, 402 for insufficient credits, etc.) surface here as the leading numeric code in the error string. See Error Codes for the full set.

Cancelling a job

You can request cancellation of an in-flight job with DELETE /v1/jobs/{job_id}. A successful cancellation marks the job as cancelled in GET /v1/jobs/recent. Note that a direct GET /v1/jobs/{job_id} on a cancelled job currently returns status: failed rather than cancelled; the listing endpoint is authoritative for the cancellation state.

curl -X DELETE "https://apis.hyperbots.com/v1/jobs/9b330094-505d-480c-812c-86a698a3ff02" \
  -H "X-API-Key: hk_live_your_key_here"

Cancellation is best-effort: a job that has already started its model invocation may complete before the cancel signal lands. You won't be billed for cancelled jobs that didn't consume model time, but billing for a job that completed microseconds before your cancel request is final.

End-to-end example

Upload, submit asynchronously, and poll to completion:

# 1. Upload (always synchronous — returns a presigned URL)
DOC_RESP=$(curl -sS -X POST https://apis.hyperbots.com/v1/documents/upload \
  -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
  -d '{"filename":"contract.pdf","content_type":"application/pdf"}')
DOC_KEY=$(echo "$DOC_RESP" | jq -r .document_key)
UPLOAD_URL=$(echo "$DOC_RESP" | jq -r .upload_url)

# 2. PUT the bytes directly to S3 (presigned URL, no auth header)
curl -sS -X PUT --data-binary @contract.pdf \
  -H "Content-Type: application/pdf" \
  -H "x-amz-server-side-encryption: AES256" \
  "$UPLOAD_URL"

# 3. Submit the parse job asynchronously
JOB=$(curl -sS -X POST "https://apis.hyperbots.com/v1/parse?ocr_engine=paddle" \
  -H "X-API-Key: $API_KEY" -H "X-Async: true" \
  -F "document_key=$DOC_KEY")
JOB_ID=$(echo "$JOB" | jq -r .job_id)

# 4. Poll every 3 seconds until status != "pending"
while true; do
  RESP=$(curl -sS "https://apis.hyperbots.com/v1/jobs/$JOB_ID" \
    -H "X-API-Key: $API_KEY")
  STATUS=$(echo "$RESP" | jq -r .status)
  if [ "$STATUS" != "pending" ]; then
    echo "$RESP" | jq
    break
  fi
  sleep 3
done

The Python SDK handles submit + poll transparently — calling client.parse(file) blocks until the result is ready, but every individual HTTP request stays sub-second under the hood. See the SDKs page for installation and tuning knobs.

Job retention

Job records live in Redis with a 24-hour TTL. After the TTL expires, polling the job_id returns a 404. If you need to keep the result, fetch it once and persist it on your side — the result is also tied to the underlying document via request_id, which lasts longer in your usage records.

Recent jobs

Need to recover a job you submitted but lost the job_id for (browser tab closed, process crashed)? Use GET /v1/jobs/recent to list the most recent jobs for your API key within the 24-hour window. This is the same endpoint that powers the dashboard's Recent Runs panel.

curl "https://apis.hyperbots.com/v1/jobs/recent?limit=10" \
  -H "X-API-Key: hk_live_your_key_here"

Using the Python SDK? You usually don't need to think about any of this. The SDK does submit + poll transparently for every long-running operation. See the SDKs page for the explicit submit_*/wait_for_job pattern if you want fire-and-forget control.