BIQE API
Integrate handwriting recognition with LLM correction directly into your own pipeline. REST-based, JSON responses, authentication via API key.
The BIQE API is built for production use: you submit pages programmatically, track their status and fetch the result — without opening the portal. Processing runs the same chain as the portal (layout detection, recognition, LLM correction, export to PageXML/ALTO/PDF).
Authentication
Every request authenticates with an API key. You can pass the key in the X-API-Key header, or in an Authorization: Bearer header — both are accepted. Traffic runs over encrypted HTTPS.
X-API-Key: biqe_live_xxxxxxxxxxxxxxxxxxxxxxxx # or, equivalently: Authorization: Bearer biqe_live_xxxxxxxxxxxxxxxxxxxxxxxx
Treat keys like passwords. Never commit them to version control and never share them by email or chat. Store them in a secret manager. Rotate them every 90 days as routine, and immediately if you suspect exposure.
Managing API keys
You create and manage keys yourself in the customer portal under Settings ▸ API keys ▸ + New API key. Give the key a label. The value is shown once on creation — it cannot be displayed again afterwards. Revoking is on the same page and takes effect immediately.
Submitting a scan
Submit pages as a ZIP archive (one image per page), in the multipart field scan_zip. Optionally pass a processing tier, preset and webhook URL.
curl -X POST https://ocr-handwriting.online/v1/process-scan \ -H "X-API-Key: $BIQE_API_KEY" \ -F "scan_zip=@my-pages.zip" \ -F "llm_tier=balanced" \ -F "preset=generic_historical" \ -F "webhook_url=https://your-system.example/biqe-callback"
Response — 202 Accepted:
{
"job_id": "ff6f3db8-ab61-47d0-9f69-1987b5b0f2f0",
"state": "queued",
"page_count": 14,
"created_at": "2026-05-02T11:56:05Z",
"expires_at": "2026-05-09T11:56:05Z",
"status_url": ".../v1/process-scan/ff6f3db8-...",
"result_url": ".../v1/process-scan/ff6f3db8-.../result"
}
Password-protected ZIP files are rejected. Use the status_url from the response to track progress.
Tracking job status
Poll the status, or wait for the webhook callback (recommended for larger volumes).
curl https://ocr-handwriting.online/v1/process-scan/<job_id> \ -H "X-API-Key: $BIQE_API_KEY"
Response (fields depend on progress):
{
"job_id": "ff6f3db8-...",
"state": "done",
"page_count": 14,
"current_step": "export",
"created_at": "2026-05-02T11:56:05Z",
"started_at": "2026-05-02T11:56:09Z",
"finished_at": "2026-05-02T11:57:43Z",
"expires_at": "2026-05-09T11:56:05Z",
"error_message": null,
"warning_message": null,
"result_url": ".../v1/process-scan/ff6f3db8-.../result"
}
The job is only finished at status done — not complete. The result_url field appears only once the status is done.
Fetching the result
Once the status is done, download the result archive via this endpoint (or via the result_url): PageXML, ALTO and — if enabled — a searchable PDF. The link stays available for about 7 days (see expires_at).
curl -L https://ocr-handwriting.online/v1/process-scan/<job_id>/result \ -H "X-API-Key: $BIQE_API_KEY" \ -o result.zip
Cancelling a job
A job that is still queued or processing can be cancelled. Its status then moves to cancelled.
curl -X DELETE https://ocr-handwriting.online/v1/process-scan/<job_id> \ -H "X-API-Key: $BIQE_API_KEY"
Webhook callback & retries
If you pass a webhook_url, we send a POST with JSON once the job reaches a terminal state (done or failed). The callback carries these headers:
| Header | Contents |
|---|---|
Content-Type | application/json |
X-BIQE-Event | Event type, e.g. job.completed. |
X-BIQE-Signature | HMAC-SHA256 signature of the body (see below). |
The payload:
{
"job_id": "ff6f3db8-...",
"state": "done",
"page_count": 14,
"result_url": ".../v1/process-scan/ff6f3db8-.../result"
}
result_url is present only when state equals done. Failed deliveries are retried up to five times with exponential back-off (about 1, 5, 25, 125 and 625 seconds). Your endpoint should return a 2xx as quickly as possible and must be reachable over HTTPS.
Verifying the webhook signature
Every callback is signed. The signature is an HMAC-SHA256 over the raw request body, keyed with your per-account webhook secret, as lowercase hex in the X-BIQE-Signature header. The secret (64 hex characters) is per customer; retrieve it via the portal.
import hmac, hashlib secret = "<your-webhook-secret>".encode() body = request.get_data() # raw bytes expected = hmac.new(secret, body, hashlib.sha256).hexdigest() if not hmac.compare_digest(expected, request.headers["X-BIQE-Signature"]): return "forbidden", 403
Compute the signature over the raw body bytes, before parsing. Re-serializing changes the bytes and makes verification fail.
Job states
| State | Meaning |
|---|---|
| queued | Accepted; waiting for a worker. |
| processing | Pages are being processed (see current_step). |
| done | All pages succeeded; result_url available ~7 days. |
| failed | Critical errors. Not billed. See error_message. |
| cancelled | Cancelled by the customer via DELETE. |
Parameters of process-scan
| Field | Required | Description |
|---|---|---|
scan_zip | yes | ZIP with one image per page (no password). |
llm_tier | no | cheap, balanced (default) or best. |
preset | no | Document type for the correction, e.g. generic_historical. |
webhook_url | no | HTTPS URL for the callback. Empty = no callback. |
pipeline_steps | no | Steps to run; default all. |
pdf_format | no | Format of the searchable PDF; default pdfa-2b. |
Error handling
| Code | Meaning |
|---|---|
202 | Request accepted; job queued. |
200 | Status retrieved (GET). |
401 | Missing/invalid key. |
400 | Invalid input (e.g. password-protected ZIP). |
404 | Unknown job_id. |
Failed jobs (failed) are not billed. When in doubt, check the status via GET /v1/process-scan/<job_id> or in the portal.