Skip to content

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.

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.

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.

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.

data must validate against the JSON Schema matching the submitted documentType:

  • purchase-order.schema.jsondocumentType: purchase_order
  • goods-received-source.schema.jsondocumentType: 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 a kind classification (invoice / goodsIssuedNote / other), item prices (priceNet, valueNet, vatRate), and deliveryCostNet — the transport cost, returned instead of a service line in items

Rules common to both schemas:

  • Every field is optional — omit whatever the parser did not read.
  • An omitted scalar is treated as null, omitted items as [].
  • Unknown keys are ignored.
  • Dates are strings in YYYY-MM-DD format.
  • 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.