POST /api/documents/extract¶
Takes the plain text of a medical document and returns a structured JSON object — patient, insurance, sender, recipient, diagnoses, medication and so on. Synchronous, no streaming; a typical document takes 2–5 seconds.
Request¶
{
"document_type": "zuweisung",
"text": "Praxis Dr. med. Anna Beispiel, Bahnhofstrasse 4, 8001 Zürich\n\nZuweisung vom 12.08.2026\n..."
}
document_type—"zuweisung"(referral/registration to an institution) or"arztbrief"(discharge/progress letter, findings). Determines the extraction schema.text— the document as plain text, 1–20 000 characters. PDFs and scans have to be converted to text on your side (OCR output is fine, see below).
Response¶
{
"document_type": "zuweisung",
"data": {
"dokument": { "typ": "zuweisung", "datum": "2026-08-12" },
"patient": {
"nachname": "Muster",
"vorname": "Max",
"geburtsdatum": "1971-02-03",
"geschlecht": null,
"strasse": "Seestrasse 12",
"plz": "8802",
"ort": "Kilchberg",
"versichertennummer": null,
"fallnummer": null
},
"versicherung": { "name": "Helsana", "klasse": "allgemein" },
"absender": {
"name": "Anna Beispiel",
"institution": "Praxis Dr. med. Anna Beispiel",
"fachrichtung": "Allgemeine Innere Medizin",
"ort": "Zürich"
},
"empfaenger": { "name": null, "institution": null, "ort": null },
"aufenthalt": { "von": null, "bis": null, "art": null },
"fragestellung": "Indikation zur Knie-TP rechts?",
"diagnosen": ["Gonarthrose rechts", "Arterielle Hypertonie"],
"medikation": ["Lisinopril 10mg 1-0-0", "Ibuprofen 400mg bei Bedarf"],
"kostengutsprache": null,
"dringlichkeit": "elektiv"
},
"removed_by_grounding": [],
"model": "eu.anthropic.claude-haiku-4-5-...",
"usage": { "tokens_in": 2149, "tokens_out": 452 },
"processing_time_ms": 2815.98
}
data— the extracted object, always with all top-level keys present. Anything not stated in the document isnull(or an empty list). Field names are German, values are taken from the document.removed_by_grounding— fields the validator dropped, see below. Empty list in the normal case.model,usage,processing_time_ms— which model answered, token counts, server-side duration.
Dates are normalised to ISO YYYY-MM-DD. Diagnoses and medication come back close to the document wording, one entry each.
Fields per document type¶
Both types share dokument, patient, versicherung, absender, empfaenger, aufenthalt, fragestellung, diagnosen, medikation.
zuweisung |
arztbrief |
|
|---|---|---|
| additional fields | kostengutsprache, dringlichkeit |
eingriffe, procedere |
Enum values: patient.geschlecht m/w · versicherung.klasse allgemein/halbprivat/privat · aufenthalt.art stationaer/ambulant · dringlichkeit notfall/dringlich/elektiv · kostengutsprache liegt_vor/beantragt/wird_eingeholt. Each of them is null unless the document says so explicitly — a clinic called "Privatklinik" does not make the insurance class private, and a requested appointment date does not make a referral urgent.
Grounding — why values cannot be invented¶
After the model has answered, a deterministic validator checks every value against the source document. Whatever it cannot find is set to null and listed in removed_by_grounding:
- Literal fields (names, addresses, numbers, dates, diagnoses, medication) need a match in the document. Dates are checked in all common notations, numbers by their digit sequence, free text by substring or ≥ 60 % token overlap.
- Enum fields need an explicit keyword in the document.
What this guarantees: an invented value never reaches you. What it does not catch: a value that is in the document but was put in the wrong field — a practice address landed in the patient address, for instance. Both values are provably in the document, so the check passes. Plan for a human review step on the fields that matter, and show the source document next to them.
A non-empty removed_by_grounding is a useful review signal: it means the model claimed something the document does not support.
OCR and scans¶
Text from OCR works. Obvious character-level OCR errors are corrected, everything else is taken as found. The weaker the OCR, the more fields end up null — which is the intended failure mode.
Example¶
curl -X POST https://medical-api.dev.unomed.ch/api/documents/extract \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"document_type":"zuweisung","text":"Zuweisung vom 12.08.2026 ..."}'
Errors¶
401 key missing/invalid · 422 validation failed (e.g. unknown document_type, text empty or > 20 000 characters) · 429 rate limit or daily quota reached · 502 the model did not return a usable extraction · 503 service not configured. See Errors & Health.
Error messages are deliberately generic — provider errors can quote parts of the request, which would mean patient data in an error string.