terra-lab-reports
Best practices and API reference for Terra API Lab Reports (pre-release) – convert clinical lab report PDFs and images into structured, standardized biomarker data. Use when parsing blood test results, extracting biomarkers, mapping to LOINC or UCUM unit codes, handling reference ranges, handling lab_report.completed / lab_report.failed webhooks, or working with PDF lab results and lab report OCR. Covers the async upload/standardize/deliver lifecycle, the event envelope (event_id, upload_id), the layered result model (source, biomarker, measurement, interpretation, reference ranges), unmatched-biomarker handling, snowflake IDs, and idempotent webhook processing.
How do I install this agent skill?
npx skills add https://github.com/tryterra/agent-skills --skill terra-lab-reportsIs this agent skill safe to install?
- Gen Agent Trust Hubpass
The skill provides comprehensive documentation and best practices for the Terra Lab Reports API. It includes API references, webhook handling guides, and clinical data standardization rules. All identified external resources and CLI tools are official vendor assets, and the ingestion surface for external files is inherent to the skill's primary medical data extraction purpose.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Terra API Lab Reports Best Practices
Production guidelines and API reference for building with the Terra API Lab Reports (pre-release). It converts clinical lab report PDFs and images into structured, standardized biomarker data: OCR plus AI extraction, then fuzzy matching against a reference dataset of 4,000+ biomarkers that yields canonical biomarker keys, UCUM unit codes, and LOINC codes.
Pre-release: assume the contract may move. Fetch the live docs pages before generating production request bodies or webhook parsers, and verify against the live API before shipping.
Start from an example app
For a new app, start from the published example for lab-report uploads and biomarker trends, and adapt it to the user's requirements. Run terra version and, if the CLI is missing, offer to install it (see below). Then list the current catalog and clone the closest fit:
terra examples list --select name,title,description
terra examples clone lab-reports-web-app my-app
Neither needs a login. Read the downloaded README and AGENTS.md and follow next_steps to install dependencies, configure and run it before building on it. Cloning only downloads files. For an existing app, use the example as a reference and adapt the relevant pieces in place. In a chat assistant, which cannot run the CLI, give the user these commands to run in their own terminal, or link the source at https://github.com/tryterra/terra-examples/tree/main/examples/lab-reports-web-app.
Account tools: CLI or MCP
Every Lab Reports endpoint is reachable by hand, which is the fastest way to answer "where did that report go":
terra data-api /lab-reports -q reference_id=<ref>
terra data-api /lab-reports/<session_id> # status and parsed results
terra data-api /lab-reports/<session_id>/deliveries # per destination: status, attempts, last error
terra data-api /lab-reports/<session_id>/files # the input files behind the session
deliveries is the one to reach for first when a report parsed but never arrived: it separates "we never delivered it" from "we delivered it and your endpoint rejected it", which are different bugs. An upload can fan out to several sessions, so learn the session ids from the webhook events or from /lab-reports?upload_id=... rather than assuming one upload is one session.
Read what an endpoint takes with terra admin-api list --data-api /lab-reports --format json.
Every command above is terra data-api, which only the CLI has; over MCP, no admin tool reads lab report sessions, so without the CLI, reach these endpoints from the application's own code.
In a coding agent (Claude Code, Codex, Cursor and the like, on the user's machine or in the cloud), use the CLI, and offer to install it if it is missing: curl -fsSL https://cli.tryterra.co/install.sh | sh on macOS and Linux, irm "https://cli.tryterra.co/install.ps1" | iex in Windows PowerShell, or Homebrew or npm where the user already uses them. In a chat assistant (claude.ai, Claude Desktop, ChatGPT), use the Terra API admin MCP server, even when it can run code: its sandbox cannot install the CLI or sign in. Each admin command is a tool whose name and method spell it (terra users list is users_read with method: "list"). Only the CLI has terra data-api, terra admin-api and terra examples, and --select and --jq have no MCP equivalent. The terra-cli and terra-mcp skills carry the guardrails (confirming changes, keeping credentials out of transcripts), the errors, and a playbook per task. They administer the integration; they do not replace the API calls this skill describes.
What the Product Does
Upload a clinical lab report and receive structured biomarker results – extracted, standardized, and delivered to your destinations or available via REST.
- Input: one file per request – PDF, PNG, JPEG, GIF, or WebP, up to 20 MB.
- Extraction: OCR plus AI parsing pulls each result's name, value, units, flags, and reference ranges off the page.
- Standardization: each extracted name is fuzzy-matched (threshold 0.85) against the 4,000+ entry biomarker dataset, producing a canonical biomarker key, a UCUM-compliant
ucum_code, and (for the subset with a LOINC mapping) aloinc_code. - Output: a Session containing layered Results (source / biomarker / measurement / interpretation / reference ranges) grouped under report-level Panels, delivered as a
lab_report.completedevent (orlab_report.failedon terminal failure).
Async Workflow and Status Lifecycle
Processing is fully asynchronous. Upload returns immediately with 202 and an upload_id (current_status: "processing") and the report moves through:
processing → processed → standardizing → standardized → sending → sent
Other statuses: retry_scheduled, retrying, cancelled, deleted, and failed. Terminal states are sent, failed, cancelled, deleted.
Results are queryable once the session reaches standardized – before delivery. You do not have to wait for sent to read them. Learn the outcome via the webhook events (lab_report.completed on success, lab_report.failed on terminal failure) or by polling. Processing typically takes 30 seconds to 3 minutes.
Endpoints
Base URL https://access.tryterra.co/api. Every request needs both headers: dev-id and x-api-key.
| Method | Endpoint | Notes |
|---|---|---|
| POST | /v2/lab-reports | Multipart upload; field MUST be file (singular), one file/request. 202 returns upload_id + current_status: "processing" – NO session_id (one upload can fan out to several sessions). |
| GET | /v2/lab-reports | List / filter (by reference_id, upload_id, report_date_from/_to, uploaded_at_from/_to). Capped at 500 – use filters. |
| GET | /v2/lab-reports/{session_id} | Session: metadata, upload_id, status history, layered results[], panels[]. Files, artifacts, and delivery state are NOT embedded – use the sub-resources. |
| GET | /v2/lab-reports/{session_id}/files | Input files + thumbnail as presigned URLs, with a response-level expires_at. |
| GET | /v2/lab-reports/{session_id}/deliveries | Per-destination delivery state (destination_id, status, attempt_count, last_error). |
| GET | /v2/lab-reports/{session_id}/artifacts | Processing artifacts; privileged keys only – standard keys get 404. |
| POST | /v2/lab-reports/{session_id}/reprocess | Re-run extraction/standardization; async 202. Emits a NEW event (new event_id) with the SAME session_id. |
| DELETE | /v2/lab-reports/{session_id} | Soft-delete; 204. |
references/api-reference.md routes each goal to its endpoint and notes the semantics the spec page treats lightly; read it before implementing any endpoint call. For full request/response/error specs (RFC 7807 shapes, status codes), fetch the live page when building the call: API Reference (append .md for markdown).
Data Model
A hierarchy: Session → Results[] + Panels[], where each Result is layered.
- Session –
session_id(snowflake string),upload_id, optionalreference_id,current_status,report_type, report/collection dates and times,report_locale,lab_name,patient_sex,patient_age_at_collection,status_history[],results_count,file_count,input_bytes,output_bytes,report_notes,results[],panels[]. - Panel – report-level grouping:
{ id, name, key }. Results reference it viabiomarker.panel_id. - Result – four layers, identical between the webhook payload and GET:
source– what the report literally printed:name,panel,value(raw string),units,flag(verbatim),method,notes,reference_text(pluscollection_date/collection_timeon the webhook copy only – GET carries those on the session).biomarker– normalized identity:key(null when unmatched),display_name,loinc_code,panel_id,panel_key,specimen.measurement– one typed value:typenames the populated sibling (numeric,bounded{operator, value},qualitative{text, code},text, or absent withabsent_reason), plusunitsanducum_code.interpretation– the abnormality layer:flag(coded, nullable),flag_raw,source,applied_range{lower, upper}(the range the flag was judged against).
- Reference Range –
lower,upper,type(a range-type label describing the range, not a verdict), and acontext(sex,age_lower/age_upper,pregnancy_status,cycle_phase,gestational_week_lower/_upper,reference_population,modifiers[]).
Webhook envelope semantics are in references/webhook-payload.md; for field-level types and the enum tables (which are open and grow over time), fetch the live Core Concepts page (append .md for markdown).
Key Data Semantics
biomarker.keyis present-but-NULL when unmatched – it is the sole no-match signal. Never key offloinc_code(it can be null even on a match). Fall back tosource.name/biomarker.display_nameand keep the result.- Branch on
measurement.typeand read the matching field. Do not probe multiple value fields in priority order – the flatvalue/value_gt/value_lt/qualitative_valuefields no longer exist; a bound like<0.5arrives asbounded: { "operator": "lt", "value": 0.5 }. - Two flags, two meanings.
source.flagis the raw lab string ("H","↑", ...);interpretation.flagis the coded signal (nullable – null means no signal), withflag_rawechoing the verbatim form andsourcenaming its provenance. - Enums are open.
current_status,report_type,measurement.type,specimen, rangetype,patient_sexmay gain new values without a major version bump. Handle unknowns gracefully (default/unknown category, never throw). - All enum values are clean lowercase strings (
"sent", not"REPORT_STATUS_SENT"or6). All timestamps are ISO-8601 UTC.
Gotchas
- Upload returns
upload_id, NOTsession_id. The202body is{ "upload_id": "...", "current_status": "processing" }. One upload can fan out to several sessions; learn session IDs from the webhook'sdata.session_idorGET /v2/lab-reports?upload_id=....upload_idcan be absent on older sessions – treat it as optional. - Two webhook events; branch on the envelope
type.lab_report.completedon success,lab_report.failedon terminal failure. Per-destination opt-in still uses the single coarse type"lab_report"indestination_event_types– both events share it. - Dedupe on
event_id. Redeliveries of the same stored payload carry the SAMEevent_id; a reprocess is a new event with a NEWevent_idand the SAMEsession_id. See rules/webhooks-dedupe-event-id.md. - Failures carry a structured error.
data.erroris{ code, message, retriable }with codesfile_unreadable,extraction_failed,standardization_failed,internal.retriable: truemeans the same input may succeed on re-submission;falsemeans fix the input first. interpretation.applied_rangecan be null whilereference_rangesare populated. It is only set when exactly one range unambiguously applies to the patient; when it is null, filterreference_rangesby context yourself (see rules/data-filter-ranges-by-demographics.md).- Presigned file URLs live on sub-resources and expire.
GET .../files(and privileged.../artifacts) return presigned URLs with a response-levelexpires_at(roughly one hour out). They are not embedded in the webhook or the session response. Re-mint by re-fetching the sub-resource; do not persist a URL past itsexpires_at. - Artifacts are privileged-only, as a
404. Standard keys get404fromGET .../artifacts, not an empty list. - Oversize uploads: a file over 20 MB is normally rejected with
400("invalid multipart form or file too large") via the byte-limit reader; a413only fires when the multipart part declares a Content-Length over the cap. Expect400(sometimes413).
Rules
Read the relevant rule in rules/ before writing that code. Each is a standalone file with incorrect/correct examples.
Webhooks & Delivery (HIGH)
webhooks-dedupe-event-id– Key idempotency onevent_id; redeliveries reuse it, a reprocess mints a new one with the samesession_id.api-poll-at-most-every-5s– Webhooks for production (success AND failure events); polling for dev/fallback; poll no faster than every 5 seconds and stop on a terminal status.
Data Handling & API Usage (HIGH–MEDIUM)
data-keep-unmatched-biomarkers– A nullbiomarker.keyis clinically relevant; never discard the result, fall back tosource.nameand flag for review.data-filter-ranges-by-demographics– Useinterpretation.applied_rangewhen present; otherwise filterreference_rangesby sex/age/context before interpreting; handlemeasurement.boundedconservatively.data-store-ids-as-strings– Snowflakesession_ids lose precision as JS numbers; keep them strings end to end.data-parse-utf8– Data containsµ,×,±, superscripts; always parse as UTF-8.api-space-bulk-uploads– One file per request, 20 MB cap, 30s–3min each; space bulk uploads and tag withreference_id.
References
Load these for full specs (each is self-contained):
- references/api-reference.md – goal-to-endpoint routing plus the semantics the spec page treats lightly. Read before calling any endpoint; fetch the linked live page for full request/response shapes.
- references/webhook-payload.md – the event envelope (
type,event_id,occurred_at,upload_id,data), the idempotency keys, and the failure event. Read when building a webhook handler. - references/biomarkers.md – standardization, UCUM mappings, LOINC coverage, and common-biomarker tables by category. Read when working with keys, units, or LOINC codes.
The full biomarker dataset (4,000+ entries, ~700 KB) is not bundled; download it from the Biomarker Reference page.
Live Documentation
Ask terra docs ask (or docs_ask over MCP) before fetching a page: it answers from the docs and cites its sources. Append .md to any docs URL for markdown.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/tryterra/agent-skills/terra-lab-reports">View terra-lab-reports on skillZs</a>