For the complete documentation index, see llms.txt. This page is also available as Markdown.

Core Concepts

Session lifecycle, data model, biomarker standardization, and async processing.

⚠️ Pre-Release

This page is currently under active development and is provided in a pre-release state.

Some content may evolve as we continue to iterate on the implementation based on your feedback, but the core concepts and functionality are expected to remain consistent.

The page will be marked as general release in the near future

Session Lifecycle

Every lab report upload creates a session. The session tracks the report through extraction, standardization, and delivery.

                          ┌──────────────────────────────┐
                          │         Upload File          │
                          │   POST /v2/lab-reports       │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │        processing            │
                          │  OCR + AI extraction of      │
                          │  biomarker data from file    │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │        processed             │
                          │  Raw results extracted       │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │      standardizing           │
                          │  Fuzzy matching biomarkers,  │
                          │  normalizing units & ranges  │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │      standardized            │
                          │  Results ready (queryable)   │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │        sending               │
                          │  Delivering to destinations  │
                          └──────────────┬───────────────┘


                          ┌──────────────────────────────┐
                          │          sent                 │
                          │  Delivered (terminal)        │
                          └──────────────────────────────┘

    At any point, a failure moves the session to:

                          ┌──────────────────────────────┐
                          │         failed               │
                          │  Error (terminal)            │
                          └──────────────────────────────┘

Status Reference

Status
Description
Terminal
Results Available

processing

Extracting biomarker data from the file

No

No

processed

Extraction complete

No

No

standardizing

Matching biomarkers and normalizing units

No

No

standardized

Standardization complete, results queryable

No

Yes

sending

Delivering the report to your configured destinations

No

Yes

sent

Delivered to all opted-in destinations

Yes

Yes

partially_sent

Delivered to at least one destination, failed others

Yes

Yes

failed

An error occurred (check status history for info)

Yes

Partial/No

retry_scheduled

Retry has been scheduled

No

Partial/No

retrying

Retry in progress

No

Partial/No

cancelled

Processing was cancelled

Yes

Partial/No

deleted

Session has been deleted

Yes

No

Status values are clean lowercase strings like "sent", "processing", "failed" — not proto-style names like "REPORT_STATUS_SENT".

Data Model

The data model is a hierarchy: Session -> Results -> Reference Ranges. Each result is grouped into four layers — source (verbatim from the report), biomarker (normalized identity), measurement (one typed value), and interpretation (coded abnormality signal).

Session Object

Field
Type
Description

session_id

string

Snowflake int64 ID (as string)

upload_id

string

The upload this report came from

reference_id

object?

Your external identifier (from upload query param)

current_status

string

Current session status (e.g. "sent")

report_type

string

Report type (e.g. "lab")

report_date

object?

Date from the lab report (YYYY-MM-DD)

report_time

object?

Time from the lab report (HH:MM 24-hour)

collection_date

object?

Specimen collection date (YYYY-MM-DD)

collection_time

object?

Specimen collection time (HH:MM 24-hour)

uploaded_at

object?

Session creation time (ISO-8601)

updated_at

object?

Last update time (ISO-8601)

report_locale

object?

Locale of the report (e.g. "ja-JP", "pt-BR")

lab_name

object?

Name of the laboratory

patient_sex

string

"male", "female", or omitted if unspecified

patient_age_at_collection

integer?

Patient age in years at specimen collection

report_notes

string

Free-text notes extracted from the report

status_history

object[]

Array of status change entries

results_count

integer

Number of biomarker results

file_count

integer

Number of uploaded files

input_bytes

integer

Size of uploaded file(s) in bytes

output_bytes

integer

Size of output data in bytes

panels

object[]

Report-level panels that results reference by panel_id (omitted if the report has no panel grouping)

results

object[]

Array of biomarker result objects

client_id is intentionally omitted from the API response. The API authenticates by client identity, so echoing it back is redundant.

Biomarker Standardization

When a lab report is processed, each extracted result goes through biomarker standardization:

  1. Fuzzy Matching — The source.name from the report is matched against Terra's reference dataset of 4,200+ known biomarkers using fuzzy string matching.

  2. Key Assignment — If a match is found, biomarker.key is set to a canonical slug (e.g., "hemoglobin", "glucose_fasting"). If no confident match exists, biomarker.key is null.

  3. Display Name — A standardized English biomarker.display_name is assigned from the matched biomarker.

  4. Unit Normalization — The source.units (as printed on the report) are mapped to a UCUM-compliant measurement.ucum_code where possible.

See Biomarker Reference for details on the matching system and a list of common biomarkers.

UCUM Unit Codes

Terra maps lab report units to UCUM (Unified Code for Units of Measure) codes wherever possible. This gives you a machine-readable, internationally standardized representation of the unit.

Report Prints

ucum_code

g/dL

g/dL

mg/dL

mg/dL

mmol/L

mmol/L

10^9/L

10*9/L

mIU/L

m[IU]/L

pg/mL

pg/mL

When UCUM mapping is not possible (e.g., proprietary lab units), measurement.ucum_code is omitted and source.units still reflects what the report printed.

Measurement Types

Every result carries a single measurement object. Read measurement.type, then read the matching field:

measurement.type

Read this field

Description

Example

numeric

measurement.numeric

A numeric measurement with optional units

Hemoglobin: 14.2 g/dL

bounded

measurement.bounded

A one-sided bound: { operator: "lt"|"gt", value: number }

PSA: <0.04

qualitative

measurement.qualitative

A categorical outcome: { text: string }

HIV screen: Non-Reactive

text

measurement.text

Free-text observation

Morphology: Normal appearance

absent

measurement.absent_reason

No value could be extracted

Value not reported

measurement.units and measurement.ucum_code describe the magnitude for numeric/bounded results. For a report showing <0.01, measurement.type is "bounded" and measurement.bounded is { "operator": "lt", "value": 0.01 }; for >5.0 it is { "operator": "gt", "value": 5.0 }.

Interpretation Flag

Each result's interpretation object carries the coded per-result abnormality signal. interpretation.flag is a normalized code (or null when there is no signal), while interpretation.flag_raw preserves the lab's verbatim flag text and source.flag carries that raw text on the source layer. interpretation.source is "report" when the flag was normalized from the printed flag, or "none" when there is no signal.

interpretation.flag

Meaning

Common raw flag(s)

normal

Not flagged

"N"

high

Above range

"H", "↑"

low

Below range

"L", "↓"

critical_high

Critically high

"HH"

critical_low

Critically low

"LL"

borderline_high

Borderline high

borderline_low

Borderline low

abnormal

Flagged, direction unspecified

"A", "C"

interpretation.flag is null when the lab did not flag that result. The raw lab string it was derived from is preserved in interpretation.flag_raw and source.flag — those are the lab's own text (e.g. "H", "↑", "Négatif"), not an enum.

Async Processing Model

Lab report processing is fully asynchronous:

  1. UploadPOST /v2/lab-reports returns 202 Accepted immediately with a session_id.

  2. Processing — Extraction and standardization happen in the background (typically 30 seconds to 2 minutes).

  3. Delivery — Once complete, the full session payload is delivered to your webhook. You can also poll GET /v2/lab-reports/{session_id} at any time.

Results become available on the GET endpoint as soon as the session reaches standardized, even before webhook delivery.

Delivery Destinations

Lab reports are not webhook-only. A completed report can be delivered to any destination type you have configured — webhooks, cloud storage (S3, GCS, Azure Blob), message queues (SQS, Kafka), and databases (MongoDB, Firestore, SQL, Supabase).

Opting a Destination In

Delivery is opt-in per destination. A destination receives lab reports only if its event-type allow-list explicitly includes lab reports — destinations that don't opt in are simply skipped (they aren't marked failed, and skipping one never affects the others).

Set the allow-list with the destination_event_types field when you create or update a destination, and include "lab_report" in it:

A destination's destination_event_types may list more than one event type. Add "lab_report" to an existing destination's array to start sending lab reports there alongside whatever it already receives.

Per-Destination Outcomes

Each destination's delivery is tracked independently. A report fanned out to several destinations records a separate outcome (pendingdelivered/failed) for each one, so a failure to one destination never hides that the report reached the others — and never fails the report as a whole.

When some destinations succeed and others fail, the session ends in the partially_sent status (terminal). Retrieve the per-destination state — including the destination_type, status, attempt_count, and any last_error — from the /deliveries sub-resource.

How Each Type Receives the Report

  • Storage destinations (S3, GCS, Azure Blob, Supabase) and document databases (MongoDB, Firestore) receive the full report body.

  • Row and message destinations (SQL, SQS, Kafka) receive a link to the report (a time-limited URL) rather than the full body — fetch the report from that link, or from GET /v2/lab-reports/{session_id}.

Timestamps and Serialization

All timestamps in responses are ISO-8601 format (e.g., "2026-03-28T14:22:10Z").

All enum values are serialized as clean lowercase strings, not proto-style names or integer codes:

  • Status: "sent" (not "REPORT_STATUS_SENT" or 6)

  • Measurement type: "numeric" (not "LAB_REPORT_RESULT_TYPE_NUMERIC")

  • Specimen: "blood" (not "LAB_REPORT_SPECIMEN_TYPE_BLOOD")

  • Reference range type: "normal" (not "LAB_REPORT_RANGE_CLASSIFICATION_NORMAL")

  • Interpretation flag: "high" (not "LAB_REPORT_FLAG_HIGH")

  • Biological sex: "male" (not "BIOLOGICAL_SEX_MALE")

The raw lab flag on source.flag (and mirrored in interpretation.flag_raw) is the lab's own string (e.g., "H", "L"), not an enum. The normalized interpretation.flag is an enum.

Optional fields that are unset are omitted from the JSON response rather than appearing as null.

Session IDs

Session IDs are snowflake int64 values. In JSON they are serialized as strings to prevent precision loss in languages like JavaScript where Number cannot represent the full int64 range.

Always store and compare session IDs as strings.

Last updated

Was this helpful?