Batch API
Process many documents in one asynchronous job. You submit a set of already-uploaded documents, get a batch_id back immediately, and poll for per-document results. The work runs on spare capacity, so a large batch never competes with your interactive calls. Supported endpoints: /v1/parse, /v1/classify, /v1/split, /v1/redact.
When to use it
Reach for batch when you have a bounded set of documents and don't need each result the instant it's ready — nightly runs, backfills, bulk imports. Instead of firing hundreds of concurrent synchronous calls (which compete for priority and get rate limited), you hand off the whole set once and collect results later. Each item is billed only on success, and the batch is durable: a worker or pod restart re-runs unfinished documents without losing or double-billing completed ones.
In exchange for that flexibility, batch is half the price of the equivalent synchronous call — every item is billed at a 50% discount — and runs on a lowest-priority lane that yields to your interactive traffic, so a large batch never competes with live calls. Batches are processed within a 24-hour SLA; most finish far sooner.
1. Submit a batch
Upload each document first via POST /v1/documents/upload to get its document_key, then submit them together. An optional Idempotency-Key header makes a resubmission return the existing batch instead of creating a duplicate. Pass an optional webhook_url to be notified when the batch finishes instead of polling — see Webhooks below.
# document_keys come from POST /v1/documents/upload (one per file)
curl -X POST "https://apis.hyperbots.com/v1/batch" \
-H "X-API-Key: hk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-2026-07-07" \
-d '{
"endpoint": "/v1/classify",
"document_keys": [
"550e8400-e29b-41d4-a716-446655440000",
"7c9e6679-7425-40de-944b-e07fc1f90ae7"
],
"options": {},
"webhook_url": "https://example.com/hooks/hyperapi"
}'# document_keys come from POST /v1/documents/upload (one per file)
curl -X POST "https://apis.hyperbots.com/v1/batch" \
-H "X-API-Key: hk_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: nightly-2026-07-07" \
-d '{
"endpoint": "/v1/classify",
"document_keys": [
"550e8400-e29b-41d4-a716-446655440000",
"7c9e6679-7425-40de-944b-e07fc1f90ae7"
],
"options": {},
"webhook_url": "https://example.com/hooks/hyperapi"
}'The response returns immediately with a batch receipt:
{
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "queued",
"total_items": 2
}{
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "queued",
"total_items": 2
}2. Check status & get results
Poll GET /v1/batch/{batch_id} for aggregate counts and per-document state. Each finished document exposes a result_url (a short-lived link to its result JSON); items keep submission order via doc_index. The batch reaches completed, completed_with_errors, failed, or canceled.
curl "https://apis.hyperbots.com/v1/batch/b1a2c3d4-0000-4a00-8000-000000000001" \
-H "X-API-Key: hk_live_your_key_here"curl "https://apis.hyperbots.com/v1/batch/b1a2c3d4-0000-4a00-8000-000000000001" \
-H "X-API-Key: hk_live_your_key_here"{
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "completed",
"endpoint": "/v1/classify",
"counts": { "total": 2, "queued": 0, "processing": 0, "done": 2, "failed": 0 },
"items": [
{ "doc_index": 0, "status": "done", "result_key": "batch-results/{org}/{batch}/0.json", "result_url": "https://..." },
{ "doc_index": 1, "status": "done", "result_key": "batch-results/{org}/{batch}/1.json", "result_url": "https://..." }
],
"created_at": "2026-07-07T09:12:04Z"
}{
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "completed",
"endpoint": "/v1/classify",
"counts": { "total": 2, "queued": 0, "processing": 0, "done": 2, "failed": 0 },
"items": [
{ "doc_index": 0, "status": "done", "result_key": "batch-results/{org}/{batch}/0.json", "result_url": "https://..." },
{ "doc_index": 1, "status": "done", "result_key": "batch-results/{org}/{batch}/1.json", "result_url": "https://..." }
],
"created_at": "2026-07-07T09:12:04Z"
}3. List & cancel
List your recent batches, or cancel one that is still running. Cancel stops queued documents; any already processing finish naturally.
curl "https://apis.hyperbots.com/v1/batch?limit=20" \
-H "X-API-Key: hk_live_your_key_here"curl "https://apis.hyperbots.com/v1/batch?limit=20" \
-H "X-API-Key: hk_live_your_key_here"4. Webhooks
If you passed a webhook_url when submitting, HyperAPI POSTs a single signed event to it once the batch reaches a terminal state, so you can react on completion instead of polling. The event is a lightweight “go look now” signal — fetch GET /v1/batch/{batch_id} for the per-document results. Delivery is rolling out gradually: provide webhook_url now and you'll begin receiving events as it is enabled for your account.
Each delivery carries these headers. The X-HyperAPI-Signature is Stripe-style: t is the Unix timestamp the event was signed at and v1 is the hex HMAC. X-HyperAPI-Delivery-Id is stable across retries of the same event, so you can dedupe.
POST /hooks/hyperapi HTTP/1.1
Content-Type: application/json
X-HyperAPI-Event: batch.completed
X-HyperAPI-Delivery-Id: 9b330094-505d-480c-812c-86a698a3ff02
X-HyperAPI-Signature: t=1779447738,v1=5f8b0c9e...c2a1POST /hooks/hyperapi HTTP/1.1
Content-Type: application/json
X-HyperAPI-Event: batch.completed
X-HyperAPI-Delivery-Id: 9b330094-505d-480c-812c-86a698a3ff02
X-HyperAPI-Signature: t=1779447738,v1=5f8b0c9e...c2a1The JSON body carries the batch outcome. The event is batch.completed when every document succeeded, or batch.completed_with_errors when at least one failed (with failed_items > 0):
{
"event": "batch.completed",
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "completed",
"total_items": 2,
"done_items": 2,
"failed_items": 0
}{
"event": "batch.completed",
"batch_id": "b1a2c3d4-0000-4a00-8000-000000000001",
"status": "completed",
"total_items": 2,
"done_items": 2,
"failed_items": 0
}Verify the signature
Always verify before trusting a delivery. Your organization has a unique signing secret (HyperAPI derives it as HMAC-SHA256(master_secret, org_id), so no other org can forge your signatures). To check a delivery: parse t and v1 from the signature header, recompute HMAC-SHA256(signing_secret, f"{t}." + raw_body) over the raw request bytes (do not re-serialize the parsed JSON — key order and spacing must match exactly), and compare in constant time. Reject the delivery if the timestamp is outside your tolerance window (guards against replay).
import hashlib
import hmac
import time
# Your organization's webhook signing secret, issued (hex-encoded) once
# webhooks are enabled for your account. Keep it server-side.
SIGNING_SECRET = bytes.fromhex("your_org_signing_secret_hex")
TOLERANCE_SECONDS = 300 # reject deliveries whose timestamp is too old
def verify(raw_body: bytes, signature_header: str) -> bool:
# Header format: "t=<unix>,v1=<hex_hmac>"
parts = dict(p.split("=", 1) for p in signature_header.split(","))
t, v1 = parts["t"], parts["v1"]
# 1. Reject replays / stale deliveries outside the tolerance window.
if abs(time.time() - int(t)) > TOLERANCE_SECONDS:
return False
# 2. Recompute the HMAC over f"{t}." + the raw (unparsed) body bytes.
signed = f"{t}.".encode() + raw_body
expected = hmac.new(SIGNING_SECRET, signed, hashlib.sha256).hexdigest()
# 3. Constant-time compare.
return hmac.compare_digest(expected, v1)import hashlib
import hmac
import time
# Your organization's webhook signing secret, issued (hex-encoded) once
# webhooks are enabled for your account. Keep it server-side.
SIGNING_SECRET = bytes.fromhex("your_org_signing_secret_hex")
TOLERANCE_SECONDS = 300 # reject deliveries whose timestamp is too old
def verify(raw_body: bytes, signature_header: str) -> bool:
# Header format: "t=<unix>,v1=<hex_hmac>"
parts = dict(p.split("=", 1) for p in signature_header.split(","))
t, v1 = parts["t"], parts["v1"]
# 1. Reject replays / stale deliveries outside the tolerance window.
if abs(time.time() - int(t)) > TOLERANCE_SECONDS:
return False
# 2. Recompute the HMAC over f"{t}." + the raw (unparsed) body bytes.
signed = f"{t}.".encode() + raw_body
expected = hmac.new(SIGNING_SECRET, signed, hashlib.sha256).hexdigest()
# 3. Constant-time compare.
return hmac.compare_digest(expected, v1)Respond 2xx quickly to acknowledge. A non-2xx response, timeout, or network error is retried with backoff; each retry is re-signed with a fresh t and reuses the same X-HyperAPI-Delivery-Id.
Parse modes
For /v1/parse batches you can add a top-level parse_mode field alongside options: "fast" (default) for standard OCR, or "advanced" for advanced layout-aware parsing that preserves the structure of dense tables and forms. Advanced parse requires a paid plan (Pro or Enterprise) — the API returns 403 otherwise — and is supported only on /v1/parse; other endpoints ignore the field. As with all batch work, advanced items run on the lowest-priority lane and yield to interactive traffic, so they may take longer under load (still within the 24-hour SLA).
{
"endpoint": "/v1/parse",
"document_keys": ["doc_a1b2", "doc_c3d4"],
"parse_mode": "advanced",
"options": {}
}{
"endpoint": "/v1/parse",
"document_keys": ["doc_a1b2", "doc_c3d4"],
"parse_mode": "advanced",
"options": {}
}Endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /v1/batch | Submit a batch of document_keys |
GET | /v1/batch/{batch_id} | Status + per-document results |
GET | /v1/batch | List your recent batches |
DELETE | /v1/batch/{batch_id} | Cancel a running batch |