Data Plane
Standardize a Report
POST /v1/standardize — lab report in, standardized indicators out; dry-run by default.
POST /v1/standardizeAuthorization: Bearer mb_live_*Document in, standardized indicators out. /v1/standardize reads a lab report or health document and returns every reading it found, each matched to a LOINC code and normalized to a UCUM unit (the mechanism is in How standardization works). The call is synchronous — one request, readings back — so a full multi-page document can take several seconds; size your client timeout accordingly.
By default it is a dry-run (store=false): you get the standardization result and nothing is persisted — zero side effects. Set store=true (with a retention) to also write those readings into the Subject’s store as records, through the same pipeline as POST /v1/data.
Request
Section titled “Request”Two input shapes:
- multipart/form-data — a
file(PDF / image / Excel / plain text; images and PDFs are OCR’d) plus the fields below as form fields. - application/json —
{"text": "..."}(raw report text) or{"file_key": "..."}(a file already uploaded via/v1/files).
| Field | Type | Description |
|---|---|---|
file / text / file_key | — | Exactly one source. A file_key that is unknown or has no extractable text → 404. |
user | string | The Subject this call runs for. |
store | bool | Default false (dry-run). true also ingests the readings. |
retention | string | Required when store=true — same enum as POST /v1/data (permanent / 1h / 2h / 6h / 1d / session). |
session_id | string | Required when retention=session. |
Dry-run (default)
Section titled “Dry-run (default)”curl https://api.mirobody.ai/v1/standardize \ -H "Authorization: Bearer $MIROBODY_API_KEY" \ -F "user=alice" \ -F "file=@lab_report.pdf"Standardize and store
Section titled “Standardize and store”curl https://api.mirobody.ai/v1/standardize \ -H "Authorization: Bearer $MIROBODY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user": "alice", "text": "Fasting glucose 97 mg/dL (2026-07-01); LDL cholesterol 120 mg/dL", "store": true, "retention": "permanent" }'Response
Section titled “Response”{ "object": "extraction", "data": [ { "indicator_raw": "Fasting glucose", "canonical_name": "Fasting glucose [Mass/volume] in Serum or Plasma", "loinc_code": "1558-6", "value_raw": "97", "parsed_value": "97", "unit_raw": "mg/dL", "unit_ucum": "mg/dL", "confidence": 0.82, "measured_at": "2026-07-01" }, { "indicator_raw": "LDL cholesterol", "canonical_name": "Cholesterol in LDL [Mass/volume] in Serum or Plasma", "loinc_code": "2089-1", "value_raw": "120", "parsed_value": "120", "unit_raw": "mg/dL", "unit_ucum": "mg/dL", "confidence": 0.79, "measured_at": null } ], "stored": false, "stored_count": 0, "subject": "alice"}| Field | Description |
|---|---|
data[] | One row per extracted reading. |
indicator_raw / value_raw / unit_raw | Exactly what the document said. |
canonical_name / loinc_code | Deterministic LOINC resolution (null = no confident code — never a guessed one). |
parsed_value / unit_ucum | Parsed value (returned as a string) + UCUM-normalized unit. |
confidence | Similarity score of the LOINC match (0–1). |
measured_at | Timestamp found in the document, if any. |
page | 1-based source page the reading came from; present for multi-page documents. |
abnormal | Abnormal-range flag carried by the source (e.g. "H" / "L" / "高"); present when non-empty. |
stored | Whether this call wrote to the store (echoes store). |
stored_count | Readings actually written (0 on a dry-run). |
dropped | Optional, top-level. Readings discarded as implausible (out-of-range or garbled values) — present when at least one was filtered out, so you can see what didn’t reach data. |
note | Present only when data is empty — explains that no quantifiable readings were found (see below). |
No quantifiable readings — narrative text
Section titled “No quantifiable readings — narrative text”/v1/standardize returns quantifiable readings. Purely narrative text — “dizzy and a headache all afternoon” — is a documented boundary, not an error: the call succeeds (200) with an empty data array and a note:
{ "object": "extraction", "data": [], "stored": false, "stored_count": 0, "subject": "alice", "note": "no quantifiable readings found in the text; for subjective/journal entries, send a single-turn POST /v1/responses with store:true instead"}Mixed text does what you’d expect — “headache all day, temperature was 38.2 °C” yields the temperature reading and drops the narrative.
Purely subjective entries (journaling) use the Agent API — no dedicated endpoint. Send each entry as a single-turn POST /v1/responses with store: true, a unique session_id such as journal-{entry_id}, builtin_tools: "none", and short-acknowledgement instructions. Mirobody then pulls any quantifiable readings and durable memories out of the stored entry automatically. With one response per session_id, deleting that response also retracts the memories it produced; readings already written to the data plane stay until you delete them through /v1/data. See Journaling for the exact lifecycle.
Errors
Section titled “Errors”| HTTP | When |
|---|---|
400 | No file / text / file_key; store=true without a valid retention; retention=session without a session_id |
404 | file_key unknown or has no extractable text |
413 | File exceeds the upload size limit |
422 | Text could not be extracted from the file |
502 | Extraction model unavailable — transient; retry with backoff |
Every failure is explicit — there is no silent partial success.