API documentation
SchemaLens is a stateless HTTP API: send a printed document and the fields you need, get the values back with a confidence score and the lines they were read from. Every call needs an API key: generate one with schemalens-api keys new and send it as Authorization: Bearer <key> or X-API-Key. You run the API yourself; this page documents your instance, with the limits you set in .env.
The examples read your instance and key from two environment variables:
export SCHEMALENS_URL=http://localhost:8000 # your instance (API_PORT in its .env)
export SCHEMALENS_KEY=sl_... # one of its API_KEYSQuick start
Send a file with a preset (or your own JSON schema) and get JSON back: every field of the schema is present, and a value that is not in the document is null.
curl -s $SCHEMALENS_URL/api/v1/extract \
-H "Authorization: Bearer $SCHEMALENS_KEY" \
-F file=@invoice.jpg \
-F preset=invoice{
"data": {
"invoice_number": "INV/12/2026",
"issue_date": "2026-10-03",
"seller_name": "Example Trading Ltd",
"gross_total": 1200.0
},
"fields": {
"gross_total": { "confidence": 0.93, "evidence": ["L011"] }
},
"usage": { "pages": 1, "ocr_ms": 2310, "llm_ms": 640, "language": "en" }
}Endpoints
| POST | /api/v1/extract | Extract fields from a document (multipart or JSON). Result as JSON, CSV or XLSX; live progress with ?stream=true. |
| POST | /api/v1/render | Turn a result you already have into CSV or XLSX, without extracting again. |
| GET | /api/v1/presets | Ready-made schemas with their fields. |
| GET | /api/v1/demo | The limits of this deployment: file size, pages, documents a day. |
| GET | /healthz | Liveness check. |
Extraction
POST /api/v1/extract takes a JPEG, PNG, WEBP, TIFF or PDF with printed text, up to MAX_UPLOAD_BYTES (4 MB by default) and MAX_PAGES (3) - both set in your .env.
Request (multipart)
| file | The document. |
| preset | Name of a ready-made schema, e.g. invoice. |
| schema | Your own JSON Schema; together with a preset it adds or overrides fields. |
| options | JSON: model_tier (fast or accurate) and include_ocr_text (true to get the lines read by OCR). |
curl -s -H "Authorization: Bearer $SCHEMALENS_KEY" $SCHEMALENS_URL/api/v1/extract \
-F file=@invoice.pdf \
-F 'schema={"type":"object","properties":{
"seller_name":{"type":"string","description":"Name of the seller"},
"issue_date":{"type":"string","format":"date"},
"total_gross":{"type":"number"},
"seller_nip":{"type":"string","x-schemalens-type":"nip"},
"cost_category":{"type":"string","enum":["fuel","services","other"],
"x-schemalens-derived":true}}}' \
-F 'options={"model_tier":"accurate","include_ocr_text":true}'The same as JSON, with the document in base64:
curl -s -H "Authorization: Bearer $SCHEMALENS_KEY" $SCHEMALENS_URL/api/v1/extract -H "Content-Type: application/json" \
-d "{\"document_base64\": \"$(base64 < receipt.jpg | tr -d '\n')\", \"preset\": \"receipt\"}"Response
| data | The values, one per schema field; null when the document does not contain it. |
| fields | Per field: confidence (0-1, computed by the service) and evidence (ids of the OCR lines). |
| warnings | Everything worth checking: normalized values, failed checksums, possible OCR noise. |
| usage | Pages, OCR and LLM time in ms, the model, the detected language, the OCR confidence. |
| ocr_text | The numbered OCR lines, only with include_ocr_text. |
{
"data": { "invoice_number": "INV/12/2026", "issue_date": "2026-10-03", "gross_total": 1230.0 },
"fields": { "gross_total": { "confidence": 0.93, "evidence": ["L011"] } },
"warnings": ["issue_date: normalized '03.10.2026' to '2026-10-03'"],
"usage": { "pages": 1, "ocr_ms": 2310, "llm_ms": 640, "model": "openai/gpt-oss-20b",
"language": "en", "ocr_confidence": 0.84, "context": "full" },
"ocr_text": ["[L001] INVOICE No. INV/12/2026"]
}Live progress
With ?stream=true the answer is NDJSON: one event per line while the document is processed. Events carry counts and timings, never the document's text.
curl -sN -H "Authorization: Bearer $SCHEMALENS_KEY" "$SCHEMALENS_URL/api/v1/extract?stream=true" -F file=@card.jpg -F preset=business_card
{"event": "accepted", "bytes": 111156, "pages": 1}
{"event": "reading", "pages": 1}
{"event": "read", "lines": 24, "ocr_ms": 3538, "ocr_confidence": 0.778, "language": "en"}
{"event": "extracting", "model": "openai/gpt-oss-20b", "fields": 9}
{"event": "result", "result": { "data": {}, "fields": {}, "warnings": [], "usage": {} }}The last event is result (the usual JSON body) or error (the same problem a plain request would get). Errors found before the work starts come as plain responses.
Schemas and presets
Send a preset, your own schema, or both. With both, the schema extends the preset:
{ "preset": "business_card",
"schema": { "type": "object", "properties": {
"industry": { "type": "string", "x-schemalens-derived": true } } } }Limits: up to 50 fields and 3 levels; types string, number, integer, boolean, array, object and enum; formats date, date-time and email. Every field is optional for the model: missing data is null, never a guess.
Extensions
| x-schemalens-type | A domain type: nip, regon, pesel, iban, phone, currency, country, postal_code_pl, url. Turns on cleaning, checksums and careful OCR fixes; every change is reported in warnings. |
| x-schemalens-derived | An inferred field (a category, a summary, the language). Written in the language of the document; its confidence is capped. |
Presets
Ready-made schemas with English field names; the documents can be in any language. GET /api/v1/presets lists their fields.
invoicereceiptcvbusiness_cardcontract_termsproduct_labelmenugeneric_documentlecture_notes
Result formats
| json | The default: data, fields, warnings, usage. |
| csv | ?format=csv: the values, one row per item of the first list; include_meta=true adds confidence columns. |
| xlsx | ?format=xlsx: a data sheet, a sheet per list and a meta sheet with confidence and warnings. POST /api/v1/render takes labels for readable headings. |
curl -s -H "Authorization: Bearer $SCHEMALENS_KEY" "$SCHEMALENS_URL/api/v1/extract?format=xlsx" -F file=@invoice.jpg -F preset=invoice -o invoice.xlsx
curl -s -H "Authorization: Bearer $SCHEMALENS_KEY" "$SCHEMALENS_URL/api/v1/extract?format=csv&include_meta=true" -F file=@invoice.jpg -F preset=invoice -o invoice.csv
curl -s -H "Authorization: Bearer $SCHEMALENS_KEY" "$SCHEMALENS_URL/api/v1/render?format=xlsx" -H "Content-Type: application/json" \
-d '{"result": { ... }, "preset": "invoice", "labels": {"gross_total": "Gross total"}}' -o result.xlsxErrors
Errors are application/problem+json (RFC 9457). The type of a problem links to its row below (…/docs#error-invalid-schema): match on the slug at its end, not on the title. Every response has an X-Request-ID.
| 401 | unauthorized | No valid key was sent: Authorization: Bearer <key> or X-API-Key. |
| 404 | not-found | No such path. |
| 405 | method-not-allowed | The path exists but not with this method. |
| 406 | not-acceptable | Unknown format; use format=json|csv|xlsx or a matching Accept. |
| 413 | document-too-large | File too large or too many pages. |
| 415 | unsupported-document | Unsupported file type: send JPEG, PNG, WEBP, TIFF or PDF. |
| 415 | unsupported-media-type | The request is neither multipart/form-data nor JSON. |
| 422 | invalid-schema | The schema was rejected; errors lists every problem. |
| 422 | invalid-request | The request is not valid (a malformed body or a missing part); errors lists what, never the rejected input. |
| 422 | document-unreadable | OCR could not read any text; the LLM is not called. |
| 429 | limit-reached | The daily limit is used up; see Retry-After. |
| 429 | too-many-requests | All processing slots are busy; see Retry-After. |
| 500 | ocr-failed | Text recognition failed. |
| 500 | internal-error | Anything unexpected; quote the X-Request-ID. |
| 502 | llm-failed | The language model did not return a usable answer. |
| 503 | llm-rate-limited | The language model provider is rate limiting; see Retry-After. |
| 503 | llm-unavailable | The service is not configured to reach a language model. |
| 504 | extraction-timeout | Processing took longer than SYNC_TIMEOUT_S. |
Run it yourself
The engine and the API are open source (Apache 2.0). In Docker on your machine - fully offline with a local model in Ollama - or on AWS with one command.
Docker
git clone https://github.com/maszjan/schemalens
cd schemalens && cp .env.example .env
uv run schemalens-api keys new --env-file .env # prints your key, adds it to API_KEYS
docker compose up --build # http://localhost:8000/docsAWS
cd infra
sam build --use-buildkit && sam deploy --guidedBehind a reverse proxy
Paths start with /v1, the API's own version, as on a dedicated address (api.example.com/api/v1/extract). Behind a reverse proxy that serves it under a prefix, set ROOT_PATH=/api for example.com/api/v1/extract; the interactive docs and the OpenAPI servers follow it.
Configuration, presets, evals and the architecture are described in the repository.