Skip to content
Get Started

Data Plane

Standardize a Report

POST /v1/standardize — lab report in, standardized indicators out; dry-run by default.

POST /v1/standardize
Authorization: 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.

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).
FieldTypeDescription
file / text / file_keyExactly one source. A file_key that is unknown or has no extractable text → 404.
userstringThe Subject this call runs for.
storeboolDefault false (dry-run). true also ingests the readings.
retentionstringRequired when store=true — same enum as POST /v1/data (permanent / 1h / 2h / 6h / 1d / session).
session_idstringRequired when retention=session.
Terminal window
curl https://api.mirobody.ai/v1/standardize \
-H "Authorization: Bearer $MIROBODY_API_KEY" \
-F "user=alice" \
-F "file=@lab_report.pdf"
Terminal window
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"
}'
{
"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"
}
FieldDescription
data[]One row per extracted reading.
indicator_raw / value_raw / unit_rawExactly what the document said.
canonical_name / loinc_codeDeterministic LOINC resolution (null = no confident code — never a guessed one).
parsed_value / unit_ucumParsed value (returned as a string) + UCUM-normalized unit.
confidenceSimilarity score of the LOINC match (0–1).
measured_atTimestamp found in the document, if any.
page1-based source page the reading came from; present for multi-page documents.
abnormalAbnormal-range flag carried by the source (e.g. "H" / "L" / "高"); present when non-empty.
storedWhether this call wrote to the store (echoes store).
stored_countReadings actually written (0 on a dry-run).
droppedOptional, 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.
notePresent 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.

HTTPWhen
400No file / text / file_key; store=true without a valid retention; retention=session without a session_id
404file_key unknown or has no extractable text
413File exceeds the upload size limit
422Text could not be extracted from the file
502Extraction model unavailable — transient; retry with backoff

Every failure is explicit — there is no silent partial success.