POST /api/deep-research¶
The ICD coding endpoint. The server keeps the connection open and streams three result tiers one after the other via server-sent events:
- Instant — semantic + keyword search, well under a second. Returns only confident hits (
high/medium, up to 10). - Smart — an LLM extracts keywords from the free text and picks codes, a few seconds.
- Deep — LLM research in the catalog with a consolidated recommendation, 10–30 s.
Tier 1 can be shown immediately and replaced or extended by tiers 2 and 3 as they arrive. If the user navigates away, just close the connection.
Request¶
query is required (1–2000 characters). A bare ICD code as query (e.g. "M17.9") is treated as a direct lookup and comes back over the same stream: tier 1 then contains the exact catalog match (high) plus neighbouring catalog codes (medium); tiers 2 and 3 list the neighbours as low with a reasoning.
rerank (optional, default off) enables a legacy cross-encoder rerank for tier 1. In that mode tier 1 codes come back with confidence: "score" and a numeric score (0–100) instead of the categorical confidence. Leave it unset unless you depend on the old format.
Response — event stream¶
Each line is an SSE event of the form data: {...}\n\n. There are three type values:
status — progress, for a spinner or status line:
If something goes wrong during the stream, you get {"type":"status","status":"error","message":"..."} — not an HTTP error but a status event. After that the request is over.
partial — results from tiers 1 and 2 (layer: 1 or 2):
{
"type": "partial",
"layer": 1,
"summary": "",
"codes": [
{
"code": "S83.53",
"description": "Riss des vorderen Kreuzbandes",
"confidence": "high",
"reasoning": "",
"score": 0
}
]
}
result — result of tier 3, after which the stream ends:
{
"type": "result",
"layer": 3,
"summary": "Short reasoning behind the recommendation",
"codes": [
{
"code": "S83.53",
"description": "Riss des vorderen Kreuzbandes",
"confidence": "high",
"reasoning": "Why this code fits",
"score": null
}
]
}
Code fields¶
code,description: ICD-10-GM code + official catalog text.confidence:"high"/"medium"for tier 1 (weaker matches are not returned),"high"/"medium"/"low"for tiers 2 and 3. Withrerank: truetier 1 uses"score"instead (legacy).score: legacy field — similarity 0–100 only whenconfidenceis"score"; otherwise0(tier 1) ornull.reasoning: short justification, tiers 2 and 3 only (tier 1 leaves it empty).
Example¶
Runs server-side (Node 18+); from there the tiers are forwarded to your own frontend, e.g. via your own SSE endpoint:
const response = await fetch('https://medical-api.dev.unomed.ch/api/deep-research', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({ query: 'Kreuzbandriss rechts' }),
});
if (!response.ok) {
// 401 / 422 / 503 arrive as regular HTTP errors, not as a stream
throw new Error(`ICD API ${response.status}: ${await response.text()}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const event = JSON.parse(line.slice(6));
if (event.type === 'partial') forwardTier(event.layer, event.codes);
if (event.type === 'result') forwardTier(3, event.codes);
if (event.type === 'status' && event.status === 'error') forwardError(event.message);
}
}
The client timeout should be at least 120 seconds — tier 3 can take a while.