Skip to main content
POST /v1/extract
Authorization: Bearer mb_live_*
Document in, standardized indicators out — synchronously. /v1/extract runs the full standardization pipeline on a lab report / health document and returns every reading it found: raw text → schema-constrained LLM extraction → deterministic LOINC resolution → UCUM unit normalization. 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 the readings into the Subject’s store through the same pipeline as POST /v1/data.

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).
FieldTypeDescription
file / text / file_keyExactly one source. file_key that is unknown or has no extractable text → 404.
userstringSubject the extraction 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.
The user field is the tenant-isolation key. The backend maps (your account, user) to an internal Subject; each user you pass is fully isolated from the others. Pass each end-user’s stable id and their data never crosses over. Omit it and the call falls back to your account’s default Subject. Subjects are invisible to the Mirobody consumer app and to other developers.

Dry-run (default)

curl https://test-mirobody-api.thetahealth.ai/v1/extract \
  -H "Authorization: Bearer $MIROBODY_API_KEY" \
  -F "user=alice" \
  -F "file=@lab_report.pdf"

Extract and store

curl https://test-mirobody-api.thetahealth.ai/v1/extract \
  -H "Authorization: Bearer $MIROBODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "user": "alice",
        "text": "Fasting glucose 5.6 mmol/L (2026-07-01); LDL cholesterol 3.1 mmol/L",
        "store": true,
        "retention": "permanent"
      }'

Response

{
  "object": "extraction",
  "data": [
    {
      "indicator_raw": "Fasting glucose",
      "canonical_name": "Fasting glucose [Moles/volume] in Serum or Plasma",
      "loinc_code": "1558-6",
      "value_raw": "5.6",
      "parsed_value": "5.6",
      "unit_raw": "mmol/L",
      "unit_ucum": "mmol/L",
      "confidence": 0.94,
      "measured_at": "2026-07-01"
    },
    {
      "indicator_raw": "LDL cholesterol",
      "canonical_name": "Cholesterol in LDL [Moles/volume] in Serum or Plasma",
      "loinc_code": "22748-8",
      "value_raw": "3.1",
      "parsed_value": "3.1",
      "unit_raw": "mmol/L",
      "unit_ucum": "mmol/L",
      "confidence": 0.91,
      "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.
storedWhether this call wrote to the store (echoes store).
stored_countReadings actually written (0 on dry-run).
notePresent only when data is empty — explains that no quantifiable readings were found (see below).

No quantifiable readings — narrative text

/v1/extract extracts 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 plus a note:
{
  "object": "extraction",
  "data": [],
  "stored": false,
  "stored_count": 0,
  "subject": "alice",
  "note": "no quantifiable readings found in the text; subjective/narrative entries (journal) are not supported yet"
}
Mixed text works as you’d expect — “headache all day, temperature was 38.2 °C” yields the temperature reading and drops the narrative. A dedicated journal endpoint for subjective/narrative entries is on the roadmap.

Errors

HTTPWhen
400No file / text / file_key; store=true without a valid retention; retention=session without 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
All failures are explicit — there is no silent partial success.