API reference

The web app on your site's origin serves the public API; the extraction engine behind it is never exposed. Every response is JSON.

Authentication

Create a key under API keys in the app (it is shown once) and send it as a bearer token. Keys work in both the cloud and the self-hosted variant.

Authorization: Bearer ak_…

Image requirements

  • One document per image or PDF page; only a PDF's first page is read. A card with its sefach sheet on one A4, or a cheque's front and back on one scan, count as one document.
  • Formats: JPEG, PNG, WebP or PDF, up to 30 MB.
  • Resolution: 150 dpi or more. Below 150 dpi reading quality is not guaranteed.
  • The whole document in the frame, flat and evenly lit, no glare; a scan beats a photo.
  • The response reports each document's estimated resolution in regions[].dpi and adds a warning below 150 dpi.

Endpoints

Extraction

MethodPathDescription
POST/api/v1/extractMultipart field file (image or PDF, ≤ 30 MB; a PDF's first page is read, further pages are reported in warnings; pages are downscaled to an A4-at-300-dpi long edge on ingest) → fields with per-field confidence, validation report, warnings, detected regions, plus meta (document_id, mode, trial_remaining, backend, model). Wait up to ENGINE_TIMEOUT_MS (10 min by default) — requests queue behind each other. Scan at 150 dpi or more (see Image requirements). Optional multipart field check_registries — lower-case true / false only (a Python True is not lower-case and gets 400 INVALID_BODY) — runs or skips the registries check for this request, whatever the settings say.
GET/api/v1/documents?limit=&offset=Paged metadata, newest first: id, timestamp, status, document type, verdict, mode, source, latency. Never document content.
GET/api/v1/documents/:idThe stored result, decrypted — only if store results was on for that request (404 NOT_STORED otherwise).
DELETE/api/v1/documents/:idDeletes that stored result → 204.
DELETE/api/v1/documentsDeletes every stored result → { "deleted": n }.
GET/api/v1/usageMode (local / trial / byok), trial counters (trial_remaining is null and unlimited true for accounts exempted by an admin), documents this month, lifetime total.
MethodPathDescription
GET/api/v1/registries/search?id=&bank=&branch=&account=&name=Searches the downloaded registries like the Registries page: filled fields combine (AND), name words match as prefixes, up to 50 rows per source with the true total. → groups[] (sources with results first: source, loaded, data_date, fetched_at, total, name_only, rows) — name_only marks an NBCTF row matched by name alone, with no ID, so verify it — and skipped[] (source, missing: the filled fields that source does not carry). Not counted as usage.

Settings

MethodPathDescription
GET/api/v1/settingsThe key owner's settings: store_results, check_registries, backend, model, local_model, plus model_choices and default_model. The Anthropic key is never shown.
PATCH/api/v1/settingsJSON with any of store_results, check_registries (booleans), model, backend, local_model → the same body as GET. The settings page's rules apply: in the cloud model must be one of model_choices; backend and local_model apply to a self-hosted instance only. A key may turn store_results off but not on (403 SESSION_REQUIRED, nothing applied; sending true while it is already on is fine): storing results is switched on in the signed-in UI only, so a leaked key cannot start keeping documents; the Anthropic key and API keys stay session-only too.

Key management (/api/keys), the settings page's own routes (/api/settings, including the Anthropic key) and the registries status (/api/registries) are UI-only: they accept the browser session and answer 403 SESSION_REQUIRED to a bearer key. A key reads and changes its owner's settings through /api/v1/settings.

Limits

  • Requests: each API key gets 30 requests a minute across /api/v1/* and a separate 20 a minute for the registries search. A burst up to the limit passes, then requests come back one at a time; over it the answer is 429 RATE_LIMITED with Retry-After in seconds. A signed-in browser session counts per user.
  • A self-hosted instance sets these with API_RATE_LIMIT_PER_MIN and API_SEARCH_RATE_LIMIT_PER_MIN (0 = no limit); the counters live in the server's memory and start over when it restarts.
  • Uploads: one image or PDF per request, up to 30 MB; only a PDF's first page is read.
  • Waiting: an extraction can take up to 10 minutes including its place in the engine's queue; a full queue answers 503 with Retry-After.
  • Trial: in the cloud a new account reads a limited number of documents on the service's own key, then 402 TRIAL_EXHAUSTED — add your Anthropic key in settings to go on.
  • Registries search: up to 50 rows per source with the true total; a query needs a number, an account or at least two letters of a name.
  • Stored results: a result is kept only while store_results is on; a key can turn it off, and only the signed-in UI turns it on.

Response shape

  • Dates are ISO everywhere; date_of_expiry of a disability_card is an ISO month (2031-03).
  • validation.overall: verified / partial / unverified / mismatch. Documents without an MRZ (old laminated cards, the disability card) are unverified by design.
  • document_type: teudat_zehut, teudat_zehut_back, teudat_zehut_sefach, israeli_passport, foreign_passport, israeli_drivers_license, cheque, cheque_back, senior_citizen_card, disability_card, weapon_license, unreadable, not_a_document.
  • A foreign_passport carries Latin names, passport number, nationality, sex and dates — no Hebrew names, no Israeli ID number. A disability_card carries names in both scripts, id_number, file_number and date_of_expiry.
  • senior_citizen_card and weapon_license are read with the generic schema: best effort, with a warning.
  • not_a_document (HTTP 200, fields: {}): no supported document on the page; regions[] lists what was seen as skipped. other appears in regions[] only.
  • Several documents on one page: one document and its companions (card + sefach, cheque front + back) are read, the rest is skipped.
  • regions[]: one entry per document found — bbox_2d is [x1, y1, x2, y2] in the range 0–1000 of the page's width and height (for a PDF, of the engine's own render), beside that region's type and its estimated resolution (dpi).
  • sefach carries the appendix sheet when the page held one (null otherwise), and usage[] the token counts of every model call, so a caller can price a request itself.
  • registries is null unless the request's check_registries field, or (when absent) the owner's setting, is on. Then: every source (id, loaded, data_date), matches[]: level (alert/info), source, by (id/account/name), field, record.
  • registries.checked lists the keys looked up in at least one downloaded source (id_number, spouse_id_number, child_id_number, drawer_id_number, guarantor_id_number, account, name_he, name_en); empty means nothing was looked up — not a clean result.
  • Name matches are whole-word matches against NBCTF individuals — verify them. A check that could not run is {"error": "REGISTRIES_UNAVAILABLE"}; it never fails the extraction.
{
  "document_type": "teudat_zehut",
  "fields": {
    "last_name_he": {
      "value": "ישראלי",
      "confidence": "high"
    },
    "first_name_he": {
      "value": "ישראל",
      "confidence": "high"
    },
    "id_number": {
      "value": "123456782",
      "confidence": "high"
    },
    "date_of_birth": {
      "value": "1990-01-31",
      "confidence": "high"
    },
    "date_of_issue": {
      "value": "2020-05-01",
      "confidence": "high"
    },
    "date_of_expiry": {
      "value": "2030-05-01",
      "confidence": "medium"
    }
  },
  "validation": {
    "mrz_present": false,
    "id_number_checksum_valid": true,
    "cross_checks": [],
    "overall": "unverified"
  },
  "warnings": [],
  "regions": [
    {
      "label": "document",
      "bbox_2d": [
        120,
        80,
        880,
        560
      ],
      "document_type": "teudat_zehut",
      "dpi": 300
    }
  ],
  "sefach": null,
  "registries": null,
  "model": "anthropic/claude-opus-5",
  "usage": [
    {
      "backend": "anthropic",
      "model": "claude-opus-5",
      "schema_name": "AnthropicPageExtraction",
      "input_tokens": 1583,
      "output_tokens": 214,
      "cache_read_tokens": 1201,
      "cache_write_tokens": 0
    }
  ],
  "meta": {
    "mode": "byok",
    "trial_remaining": null,
    "backend": "anthropic",
    "model": "claude-opus-5",
    "document_id": "0b1c…"
  }
}

Cost figures

The Cost column and the dashboard totals are computed locally from the engine's per-call token counts at Anthropic list prices as of 2026-06-24 (input, output, cache read and cache write, per model). They are an estimate: prices change and this table is updated by hand — the usage page of your Anthropic account is the bill. An Ollama document costs nothing and shows a dash.

Errors

Every error carries a machine-readable code; extra fields depend on the code.

{ "error": "TRIAL_EXHAUSTED", "detail": "…", "trial_docs": 5 }
StatusCodeDescription
401TOKEN_INVALIDUnknown, malformed or revoked key.
401UNAUTHENTICATEDNo credentials (cloud mode).
402TRIAL_EXHAUSTEDFree documents used up — add an Anthropic key in settings.
400NO_FILENo file part in the request.
400NOT_IMAGEThe file is not an image or a PDF.
400INVALID_BODYA field has the wrong type or value (e.g. check_registries is not true/false).
400INVALID_MODELmodel is not one of model_choices.
400INVALID_QUERYA registries search needs a number, an account or two letters of a name.
400ANTHROPIC_KEY_INVALIDAnthropic rejected the key saved in settings.
403SESSION_REQUIREDA session-only route was called with an API key.
404NOT_FOUNDNo such document for this account.
404NOT_STOREDThe document exists but its result was not stored.
413TOO_LARGEOver 30 MB.
429RATE_LIMITEDOver the key's request budget: 30 requests a minute on /api/v1/*, and a separate 20 for the registries search, by default. Retry after the Retry-After seconds (also retry_after in the body).
500KEY_DECRYPT_FAILEDThe server's master key changed since the value was stored.
500INTERNALUnexpected server error — retry later.
502ENGINE_MISCONFIGUREDThe engine rejected the web app's secret (deployment error).
503ENGINE_UNAVAILABLEThe engine is unreachable.
504ENGINE_TIMEOUTExtraction did not finish within ENGINE_TIMEOUT_MS.
ENGINE_ERRORThe engine's own status and detail passed through — e.g. 503 with Retry-After when its queue is full, 422 when the model refused the image.

Examples

Extract a document and check it against the registries

curl -s -X POST https://makor.pro/api/v1/extract \
  -H "Authorization: Bearer ak_…" \
  -F "file=@document.jpg" \
  -F "check_registries=true"

Search the registries

curl -s "https://makor.pro/api/v1/registries/search?id=510000003" \
  -H "Authorization: Bearer ak_…"

Read and change the settings

curl -s https://makor.pro/api/v1/settings -H "Authorization: Bearer ak_…"

curl -s -X PATCH https://makor.pro/api/v1/settings \
  -H "Authorization: Bearer ak_…" \
  -H "Content-Type: application/json" \
  -d '{"check_registries": true, "store_results": false}'
API reference · Makor