API contract
Sancho recognizes documents through a pluggable OCR backend. Parseur is the default, but a self-hosted deployment can point at its own service — it only has to expose the two HTTP endpoints described below.
If you are starting the implementation, begin with the Building an OCR backend guide.
Configuration
Section titled “Configuration”API environment variables:
| Variable | Description |
|---|---|
OCR_PROVIDER |
parseur (default) or custom. |
OCR_CUSTOM_BASE_URL |
Base address of the service, e.g. https://ocr.company.local. Required for custom. |
OCR_CUSTOM_SUBMIT_PATH |
Path of the endpoint that accepts a document. Defaults to /ocr/documents. |
OCR_CUSTOM_STATUS_PATH |
Path of the status endpoint; the job id is appended to it. Defaults to /ocr/documents. |
OCR_CUSTOM_API_KEY |
Optional token. When set, Sancho sends an Authorization: Bearer <token> header. |
OCR_POLL_INTERVAL_MS |
Delay between polls. Defaults to 3000. |
OCR_POLL_MAX_ATTEMPTS |
Maximum number of polls. Defaults to 60 (3 minutes at the default interval). |
The address is concatenated literally: OCR_CUSTOM_BASE_URL + path. The base may therefore carry its own prefix (https://ocr.company.local/api) and it will not be lost.
An invalid configuration stops the API from starting: custom without OCR_CUSTOM_BASE_URL, parseur without PARSEUR_API_KEY, and a poll budget (OCR_POLL_INTERVAL_MS × OCR_POLL_MAX_ATTEMPTS) that reaches or exceeds 10 minutes.
Endpoint 1 — submit a document
Section titled “Endpoint 1 — submit a document”POST {OCR_CUSTOM_BASE_URL}{OCR_CUSTOM_SUBMIT_PATH}
multipart/form-data request:
| Field | Description |
|---|---|
file |
Document bytes (PDF or image); the file name and MIME type are preserved. |
documentType |
purchase_order or goods_received_source. |
Response 202 (any 2xx is accepted):
{ "id": "3f1c8b12-9d4e-4a51-8b0c-1f2e3d4a5b6c", "status": "pending" }id is any identifier unique within your service — a UUID is ideal. It must be opaque and URL-safe: it goes into the status endpoint’s path, so characters like / or a space are percent-encoded (%2F, %20), and many servers reject %2F inside a path segment. Do not use document numbers.
status may be pending, processing, queued, ready or error; omitting the field means pending. Returning ready here is allowed — Sancho still makes one poll, because the submit response carries no data field.
Sancho never retries this request automatically — a repeat would mean a second job for the same document.
Endpoint 2 — poll for the result
Section titled “Endpoint 2 — poll for the result”GET {OCR_CUSTOM_BASE_URL}{OCR_CUSTOM_STATUS_PATH}/{id}
While processing:
{ "id": "3f1c8b12-9d4e-4a51-8b0c-1f2e3d4a5b6c", "status": "processing" }Once finished:
{ "id": "3f1c8b12-9d4e-4a51-8b0c-1f2e3d4a5b6c", "status": "ready", "data": { "documentNumber": "ZAM/1/2026", "items": [] }}On failure:
{ "id": "3f1c8b12-9d4e-4a51-8b0c-1f2e3d4a5b6c", "status": "error", "message": "Could not read the document" }Any non-2xx response (including 404 for an unknown id) is treated like a connection failure: Sancho keeps polling within the remaining budget and marks the document errored once that budget runs out.
Result format
Section titled “Result format”data must validate against the JSON Schema matching the submitted documentType:
purchase-order.schema.json—documentType: purchase_ordergoods-received-source.schema.json—documentType: goods_received_source— any source document a goods receipt can be inferred from: a goods-issued (WZ) note, a purchase invoice, a delivery note. Besides the header and items, the schema carries akindclassification (invoice/goodsIssuedNote/other), item prices (priceNet,valueNet,vatRate), anddeliveryCostNet— the transport cost, returned instead of a service line initems
Rules common to both schemas:
- Every field is optional — omit whatever the parser did not read.
- An omitted scalar is treated as
null, omitteditemsas[]. - Unknown keys are ignored.
- Dates are strings in
YYYY-MM-DDformat. - Numbers are JSON numbers, not strings.
- Tax ids are best sent as digits only — Sancho strips spaces and dashes anyway.
A document whose result fails validation is marked as errored (SchemaValidationError) and goes no further.