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).

BASEhttps://ocr-handwriting.online

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

POST/v1/process-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

GET/v1/process-scan/<job_id>

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

GET/v1/process-scan/<job_id>/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

DELETE/v1/process-scan/<job_id>

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:

HeaderContents
Content-Typeapplication/json
X-BIQE-EventEvent type, e.g. job.completed.
X-BIQE-SignatureHMAC-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

StateMeaning
queuedAccepted; waiting for a worker.
processingPages are being processed (see current_step).
doneAll pages succeeded; result_url available ~7 days.
failedCritical errors. Not billed. See error_message.
cancelledCancelled by the customer via DELETE.

Parameters of process-scan

FieldRequiredDescription
scan_zipyesZIP with one image per page (no password).
llm_tiernocheap, balanced (default) or best.
presetnoDocument type for the correction, e.g. generic_historical.
webhook_urlnoHTTPS URL for the callback. Empty = no callback.
pipeline_stepsnoSteps to run; default all.
pdf_formatnoFormat of the searchable PDF; default pdfa-2b.

Error handling

CodeMeaning
202Request accepted; job queued.
200Status retrieved (GET).
401Missing/invalid key.
400Invalid input (e.g. password-protected ZIP).
404Unknown 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.