SchemaLens Docs

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_KEYS

Quick 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
Response
{
  "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/extractExtract fields from a document (multipart or JSON). Result as JSON, CSV or XLSX; live progress with ?stream=true.
POST/api/v1/renderTurn a result you already have into CSV or XLSX, without extracting again.
GET/api/v1/presetsReady-made schemas with their fields.
GET/api/v1/demoThe limits of this deployment: file size, pages, documents a day.
GET/healthzLiveness 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)

fileThe document.
presetName of a ready-made schema, e.g. invoice.
schemaYour own JSON Schema; together with a preset it adds or overrides fields.
optionsJSON: 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

dataThe values, one per schema field; null when the document does not contain it.
fieldsPer field: confidence (0-1, computed by the service) and evidence (ids of the OCR lines).
warningsEverything worth checking: normalized values, failed checksums, possible OCR noise.
usagePages, OCR and LLM time in ms, the model, the detected language, the OCR confidence.
ocr_textThe 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-typeA 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-derivedAn 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

jsonThe 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.xlsx

Errors

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.

401unauthorizedNo valid key was sent: Authorization: Bearer <key> or X-API-Key.
404not-foundNo such path.
405method-not-allowedThe path exists but not with this method.
406not-acceptableUnknown format; use format=json|csv|xlsx or a matching Accept.
413document-too-largeFile too large or too many pages.
415unsupported-documentUnsupported file type: send JPEG, PNG, WEBP, TIFF or PDF.
415unsupported-media-typeThe request is neither multipart/form-data nor JSON.
422invalid-schemaThe schema was rejected; errors lists every problem.
422invalid-requestThe request is not valid (a malformed body or a missing part); errors lists what, never the rejected input.
422document-unreadableOCR could not read any text; the LLM is not called.
429limit-reachedThe daily limit is used up; see Retry-After.
429too-many-requestsAll processing slots are busy; see Retry-After.
500ocr-failedText recognition failed.
500internal-errorAnything unexpected; quote the X-Request-ID.
502llm-failedThe language model did not return a usable answer.
503llm-rate-limitedThe language model provider is rate limiting; see Retry-After.
503llm-unavailableThe service is not configured to reach a language model.
504extraction-timeoutProcessing 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/docs

AWS

cd infra
sam build --use-buildkit && sam deploy --guided

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