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[].dpiand adds a warning below 150 dpi.
Endpoints
Extraction
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/extract | Multipart 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/:id | The stored result, decrypted — only if store results was on for that request (404 NOT_STORED otherwise). |
| DELETE | /api/v1/documents/:id | Deletes that stored result → 204. |
| DELETE | /api/v1/documents | Deletes every stored result → { "deleted": n }. |
| GET | /api/v1/usage | Mode (local / trial / byok), trial counters (trial_remaining is null and unlimited true for accounts exempted by an admin), documents this month, lifetime total. |
Registries search
| Method | Path | Description |
|---|---|---|
| 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
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/settings | The 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/settings | JSON 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 is429 RATE_LIMITEDwithRetry-Afterin seconds. A signed-in browser session counts per user. - A self-hosted instance sets these with
API_RATE_LIMIT_PER_MINandAPI_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
503withRetry-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_resultsis on; a key can turn it off, and only the signed-in UI turns it on.
Response shape
- Dates are ISO everywhere;
date_of_expiryof 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 asskipped.otherappears inregions[]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_2dis[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).sefachcarries the appendix sheet when the page held one (null otherwise), andusage[]the token counts of every model call, so a caller can price a request itself.registriesis null unless the request'scheck_registriesfield, 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.checkedlists 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 }| Status | Code | Description |
|---|---|---|
| 401 | TOKEN_INVALID | Unknown, malformed or revoked key. |
| 401 | UNAUTHENTICATED | No credentials (cloud mode). |
| 402 | TRIAL_EXHAUSTED | Free documents used up — add an Anthropic key in settings. |
| 400 | NO_FILE | No file part in the request. |
| 400 | NOT_IMAGE | The file is not an image or a PDF. |
| 400 | INVALID_BODY | A field has the wrong type or value (e.g. check_registries is not true/false). |
| 400 | INVALID_MODEL | model is not one of model_choices. |
| 400 | INVALID_QUERY | A registries search needs a number, an account or two letters of a name. |
| 400 | ANTHROPIC_KEY_INVALID | Anthropic rejected the key saved in settings. |
| 403 | SESSION_REQUIRED | A session-only route was called with an API key. |
| 404 | NOT_FOUND | No such document for this account. |
| 404 | NOT_STORED | The document exists but its result was not stored. |
| 413 | TOO_LARGE | Over 30 MB. |
| 429 | RATE_LIMITED | Over 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). |
| 500 | KEY_DECRYPT_FAILED | The server's master key changed since the value was stored. |
| 500 | INTERNAL | Unexpected server error — retry later. |
| 502 | ENGINE_MISCONFIGURED | The engine rejected the web app's secret (deployment error). |
| 503 | ENGINE_UNAVAILABLE | The engine is unreachable. |
| 504 | ENGINE_TIMEOUT | Extraction did not finish within ENGINE_TIMEOUT_MS. |
| ↑ | ENGINE_ERROR | The 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}'