> For the complete documentation index, see [llms.txt](https://docs.tryterra.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tryterra.co/lab-reports/scans.md).

# Scans (DEXA)

DXA body-composition and bone-density scans go through the same upload endpoint as lab reports. This page covers what is different about the results.

Terra ingests DXA/DEXA scans — body composition and bone densitometry — through the **same** `POST /v2/reports` endpoint as clinical lab reports. There is no separate endpoint and no parameter to declare the type: Terra classifies the file on upload and tells you what it decided in `report_type`.

Everything else on this page describes the one thing that genuinely differs: a scan measures the **same quantity at many places on the body**, so a result is identified by a measure *and* a site.

## Telling the two apart

Every session and every `lab_report.completed` webhook carries `report_type`:

| Value  | What it is                                                                           |
| ------ | ------------------------------------------------------------------------------------ |
| `lab`  | Clinical laboratory report. Results are scoped by `biomarker.specimen`.              |
| `dexa` | DXA body-composition or bone-density scan. Results are scoped by `biomarker.region`. |

Branch on it before you read results. A session fetched over REST may also read `unspecified` while it is still being classified; a delivered webhook never does.

To list only one kind, pass `report_type=lab` or `report_type=dexa` to `GET /v2/reports`.

## Site-scoped results

On a lab report, a biomarker appears once. `biomarker.key` identifies the result, and that is the end of it.

On a scan it does not. A total-body scan reports **fat mass** for the head, each arm, each leg, the trunk, android, gynoid, and the whole body — one row each, same `biomarker.key` every time. What separates them is `biomarker.region`.

> The identity of a scan result is the pair **(`biomarker.key`, `biomarker.region`)**. Neither half is unique on its own.

Two fields carry the site:

* **`biomarker.region`** — the canonical key, from the closed vocabulary below. Use this for storage, joins and comparison across reports and vendors.
* **`source.region_name`** — the site exactly as the report printed it, in its own language (`"L1-L4"`, `"Bras gauche"`). Use this for display. It is present whenever the report showed a site, including when the printed label matched no canonical key — so a site Terra does not recognise is still visible to you rather than silently lost.

{% hint style="danger" %}
If you store results keyed on `biomarker.key` alone, a scan collapses to one row per measure and the rest are discarded. Nothing errors. Key on `(key, region, collection_date)` — on a lab report the two extra fields are absent and it degrades to the key alone. See [Best Practices](/lab-reports/best-practices.md).
{% endhint %}

## Serial scans in one report

Many scan reports print a trend table: the current scan alongside previous ones. Those prior rows come through as ordinary results, distinguished only by **`source.collection_date`**, which can be years earlier than the report-level `report_date`.

If you want only the current scan, filter to the latest `collection_date` rather than assuming one report means one point in time.

## Region vocabulary

`biomarker.region` is drawn from this closed set. Terra discards anything outside it before storage, so you will never see a value not on this list.

**Body composition (total-body scans)**

`head` `arms` `arm_left` `arm_right` `legs` `leg_left` `leg_right` `trunk` `trunk_left` `trunk_right` `android` `gynoid` `vat` `ribs` `ribs_left` `ribs_right` `pelvis` `spine` `spine_thoracic` `spine_lumbar` `subtotal` `total` `total_left` `total_right` `whole_body`

**Lumbar spine (bone density)**

`l1` `l2` `l3` `l4` `l1_l2` `l1_l3` `l1_l4` `l2_l3` `l2_l4` `l3_l4`

**Hip / femur (bone density)**

`femur_neck` `femur_neck_left` `femur_neck_right` `femur_trochanter` `femur_trochanter_left` `femur_trochanter_right` `femur_intertrochanter` `femur_intertrochanter_left` `femur_intertrochanter_right` `femur_wards` `femur_wards_left` `femur_wards_right` `femur_total` `femur_total_left` `femur_total_right` `femur_dual_total`

**Forearm (bone density)**

`radius_ultradistal` `radius_ultradistal_left` `radius_ultradistal_right` `radius_mid` `radius_mid_left` `radius_mid_right` `radius_33` `radius_33_left` `radius_33_right` `forearm_total` `forearm_total_left` `forearm_total_right`

Laterality is a suffix, and the vocabulary is ordered anatomy-first, so a prefix match is a valid family query: `femur_neck` matches `femur_neck`, `femur_neck_left` and `femur_neck_right`.

## Scores, negative values and units

T-scores and Z-scores are ordinary numeric results. Two things to expect:

* **They are routinely negative.** A T-score of `-2.1` is a normal reading, not an error. Do not treat a negative as a sentinel.
* **Their unit is `{SD}`** (standard deviations) in `measurement.ucum_code`. Some reports print no unit at all for a score.

Mass values come back in the units the report printed — grams on one vendor, kilograms on another. Read `measurement.ucum_code` rather than assuming.

## LOINC on scans

LOINC's DXA terms are **pre-coordinated**: the site is baked into the term, so bone mineral density at the lumbar spine and at the femoral neck are different LOINC codes for what Terra calls the same `biomarker.key`.

That means `loinc_code` on a scan is a property of the **(measure, site)** pair. Read it off each result. Caching one code against a `biomarker.key` mislabels every site but the first. Where a site has no LOINC term of its own, the field is omitted rather than filled with a code for a different site.

Coverage is partial. Bone density and the scores are coded at the standard sites; per-site body fat percentage and the mass measures (fat mass, lean mass, bone mineral content) have no LOINC term and omit the field. An absent `loinc_code` is not a matching failure — `biomarker.key` is the signal for that, and it is `null` when nothing matched.

## If a file is neither

A file Terra cannot recognise as a lab report or a scan fails permanently with error code `unsupported_report_type` and `retriable: false`. Re-uploading the same file fails the same way; it is not a transient error.

## Panels

On a scan, `panels[]` carries the scan's own sections — `"AP Spine"`, `"Total Body"` — in the same shape a lab report uses for `"CBC"`. Results reference them by `biomarker.panel_id` exactly as before.
