# Terra Vantage

> Order at-home and go-to-lab diagnostic tests and deliver results with the Terra Vantage API. Use when ordering blood tests or DNA tests, integrating at-home test kits, placing diagnostics orders, browsing test products and variants, handling kit activation, simulating order lifecycles in sandbox, tracking fulfillment and shipping, receiving results-status webhooks, fetching FHIR-format lab results, or acknowledging results (a mandatory compliance step before patients can view them). Covers the product-to-order-to-results workflow, authentication (Terra dev-id/API key via HTTP Basic or headers), the AT_HOME vs GO_TO_LAB collection methods, webhook events and HMAC signature verification, delivery debugging, the sandbox environment, and the manual partner onboarding required to get started.

- Skill: `tryterra/terra-vantage` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add tryterra/terra-vantage`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tryterra/terra-vantage/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: tryterra (https://skillmd.com/u/tryterra)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tryterra/terra-vantage

---


# Terra Vantage API

The Vantage API is a platform for managing blood test and DNA diagnostic products, processing orders, and delivering test results. It lets healthcare providers, laboratories, and partners embed diagnostic testing directly into their own applications while Terra API handles the operational complexity: kit supplier integrations (no minimum order requirements), logistics and shipping, compliance, and results standardization into FHIR format.

## From the terminal

Account configuration lives in the [Terra dashboard](https://dashboard.tryterra.co), which an agent cannot click. The `terra` CLI does the same from a terminal, with one limit worth knowing up front: **Vantage runs on its own host and the CLI does not reach it.** `terra data-api` targets the Unified API and has no Vantage endpoints, so the calls in this skill stay in your own client.

What the CLI does supply is the credential pair Vantage authenticates with, which is the same dev-id and API key as every other Terra API product:

```sh
terra environments api-key retrieve --env <dev-id> --reveal   # dev-id and API key for Basic auth
terra environments list                                       # which dev-ids exist
```

Those are the `DEV_ID` and `API_KEY` behind `Authorization: Basic base64(DEV_ID:API_KEY)` below, so an agent can fetch them rather than asking someone to copy them out of the dashboard. Sandbox and production are separate Vantage hosts but the same account credentials.

`--reveal` prints the key, and printing is the whole point of that command, so run it only when something is about to consume the value. It also returns the webhook signing secret. In CI, pipe it into the consumer rather than letting it reach the job log, and use `--json` to take only the field you need.

Install it with `brew install tryterra/tap/terra` on macOS or `npm install -g @tryterra/cli` elsewhere. The `terra-cli` skill carries the guardrails (`--reveal` on anything returning a credential, `--yes` on anything destructive), the exit codes, and a playbook per task. It administers the integration; it does not replace the API calls this skill describes.

**Availability and onboarding.** Vantage is available in the United Kingdom and the USA, with Germany, Spain, and France listed as coming soon. Onboarding is manual, not self-service: contact Terra API to have your credentials enabled (see [Account setup](https://docs.tryterra.co/vantage-api/account-setup-and-api-keys)). Access is sandbox-first; production is enabled separately when you go live.

## Authentication

Vantage uses your standard **Terra API credentials** (the `dev-id` and API key from the Terra dashboard – the same pair as every other Terra API product), presented either way on every authenticated endpoint:

1. **HTTP Basic** (what the docs show): username = `dev-id`, password = API key, i.e. `Authorization: Basic base64(DEV_ID:API_KEY)`. Older docs called these `CLIENT_ID`/`CLIENT_SECRET` – same values.
2. **Header pair**: `dev-id: <id>` + `x-api-key: <key>`.

`401` = wrong/unknown credential; `403` = suspended account, missing Vantage access, ordering a catalog-disabled product, or calling the simulate endpoint in production.

Base URLs (all API paths below are under `/api/v1`):

| Environment | Base URL                              |
| ----------- | ------------------------------------- |
| Production  | `https://vantage.tryterra.co`         |
| Sandbox     | `https://vantage-sandbox.tryterra.co` |

## Order-to-Results Workflow

An integration walks a test from catalog to acknowledged result. Products are a three-level catalog (**product types** hold **products**, which hold **variants**), and a variant is the exact thing a recipient receives.

```
1  GET  /api/v1/products                       -> list product types (e.g. Blood Test)
2  GET  /api/v1/products/{product_type_id}     -> products within a type
3  GET  /api/v1/products/{product_id}/variants -> variants of a product
4  POST /api/v1/orders                         -> place order (variant_id + quantity + recipient)
5  (webhooks)                                  -> fulfillment + results status changes
6  GET  /api/v1/results/{order_item_id}?test_taker_id= -> presigned FHIR download URL
7  POST /api/v1/results/{order_item_id}/acknowledge?test_taker_id= -> MANDATORY, user-triggered
```

Placing an order returns `201` with an `order_id`, a `recipient_id`, and one `order_item_id` per item. New orders start at `order_status: "order.payment_processing"` (`order.processing` is a later state, reached only after payment completes) and each item at `results_status: "results.awaiting_sample"`. Track everything downstream by `order_item_id`, since results, activation, and acknowledgment are all per-item.

[references/api-reference.md](references/api-reference.md) maps each goal to its endpoint (including the order/results indexes, lab lookup, catalog curation, and the monitoring endpoints) and to the live doc page carrying the current request/response schema; read it before implementing any endpoint call, and fetch the linked `.md` page when building a request body.

## Collection Methods

Each order sets a `collection_type` that determines how the sample is taken and which address field applies:

| `collection_type` | Meaning                                                                         | Address field           |
| ----------------- | ------------------------------------------------------------------------------- | ----------------------- |
| `AT_HOME`         | Kit shipped to the recipient; they self-collect and follow the kit instructions | `shipping_address`      |
| `GO_TO_LAB`       | Sample drawn at a lab draw site (Patient Service Center)                        | `requested_lab_address` |

For `GO_TO_LAB`, the requested lab address is used as a proxy to route the order to the closest available lab, and `GET /api/v1/labs?zip_code=` lists nearby draw sites (US) to offer the user beforehand. To bind the order to the user's chosen site, pass the row's `code` + `address.postal_code` as `requested_lab` on POST /orders, and the response then carries `confirmed_lab` (full site details); an unknown code 400s with `invalid_fields` tag `unknown_lab_code` (re-fetch /labs, re-select); a temporary lookup outage accepts the order unbound (`confirmed_lab` null, `requested_lab` echoed on GET). The response's `confirmed_lab_address` is the nearest draw site to the requested address (best-effort - may be `null`; informational, not a binding reservation). A variant advertises which methods it supports in `available_collection_types` (an array of the strings `"AT_HOME"`/`"GO_TO_LAB"`) and which countries it can be ordered in as `supported_ship_to_countries` (ISO-3166 alpha-2; the shipping destination for AT_HOME, the requested lab's country for GO_TO_LAB); gate both the method choice and the address form on them before ordering.

## Kit Activation

Some suppliers require the end user to activate the kit themselves (for example by scanning a QR code on the packaging) before the lab will process it. The QR code embeds `GET /api/v1/orders/activate?kit_id={supplier_item_id}`, which serves an HTML activation form; the form submits to `POST /api/v1/orders/activate` (JSON body, buildable programmatically if you own the UI). Both activation routes are unauthenticated. A successful activation emits a `results.kit_activated` webhook that carries the newly assigned **`test_taker_id` – capture it**; you need it for every results call.

## Acknowledging Results (Mandatory)

Acknowledging results is **not optional**. All results, from low to high clinical escalation, must be explicitly acknowledged before patients can access them, and the call must be triggered by an explicit end-user action (a button/checkbox after viewing) – never automatically by your backend on retrieval.

```
POST /api/v1/results/{order_item_id}/acknowledge?test_taker_id={id}
```

Two consequences are called out for leaving results unacknowledged:

- **Liability transfers to you.** Failure to acknowledge "initiates a chain of liability that transfers responsibility directly to you," and your organization assumes full liability for adverse outcomes from delayed or unacknowledged results.
- **Suppliers may contact patients directly.** Escalated results carry an `acknowledgment_due_by` deadline; if results stay unacknowledged, test suppliers and medical teams are authorized to reach out to patients directly.

FHIR result structure and the fetch/acknowledge endpoints are detailed in [references/results.md](references/results.md); read it when building result retrieval or the acknowledgment step.

## Webhooks

Terra API delivers order and result progress to the HTTPS webhook URL registered per environment (`PATCH /api/v1/clients/webhook-url`; read back with `GET`). There are two `event_type` families: `order.status_changed` for fulfillment progress and `order_item.results_status_change` for per-item result progress. Webhooks are HMAC-SHA256 signed via the `X-Terra-Signature` header; verify every request before trusting it. Delivery is at-least-once with retries – dedupe on `event_id` and respond 2xx fast.

Every event type, its full payload, the status enums, signature verification, and retry/debugging semantics are in [references/webhooks.md](references/webhooks.md). Read it when building or verifying a webhook handler, or when deliveries seem to be missing.

## Working with the Sandbox

Two ways to drive a sandbox order through its lifecycle:

- **The simulate endpoint (preferred for tests).** `POST /api/v1/orders/{orderID}/simulate` with `{"event": "...", "order_item_id": "..."}` applies one lifecycle event exactly as a supplier update would – status history recorded AND the signed webhook delivered. Events: `payment_complete`, `payment_failed`, `processing`, `delayed`, `cancelled`, `delivery_fulfilled`, `completed`, `kit_activated`, `sample_processing_in_lab`, `sample_rejected`, `partial_results_ready`, `results_ready`, `lab_processing_error`, `escalation_raised`. Only valid forward transitions are accepted (`422` otherwise); `results_ready` also stores a sample result so download/acknowledge work end to end. Returns `403` in production.
- **The simulated supplier flow.** The sandbox supplier progresses orders on timers: shipping webhooks arrive within a minute or so, then after kit activation the result webhooks follow at 1-2 minute intervals. Activation is a browser flow: open `https://vantage-sandbox.tryterra.co/api/v1/orders/activate?kit_id={supplier_item_id}` (the `supplier_item_id` arrives on the sandbox webhook) and complete the form. The happy path is deterministic (`results_ready`); use simulate to exercise rejections.

**Sandbox webhooks add `supplier_item_id`** in `order.status_changed` payloads; production payloads omit it (recover it from `GET /api/v1/orders/{orderID}` → `items[].supplier_item_id`).

## Gotchas

- **IDs are JSON strings.** `order_id`, `order_item_id`, `recipient_id`, `test_taker_id`, `variant_id`, and `event_id` in order responses and webhooks are 64-bit snowflakes serialized as strings – never parse them as numbers (JavaScript corrupts them). Catalog reads (`/products*`) still return numeric `id` fields; order requests take `variant_id` as a string.
- **One vocabulary on both surfaces.** REST reads and webhook payloads use the same `order.*` fulfillment statuses (payment failure = `order.payment_failed`) and the same `results.*` statuses - webhook statuses match REST reads verbatim. (Historic: webhooks once said `fulfillment.*` and REST said `order.failed`; both retired.)
- **Signature timestamp is Unix SECONDS.** `X-Terra-Signature: t=<unix_seconds>,v1=<hex>`; treating `t` as milliseconds makes every verification fail. Sign-check against the raw body.
- **`test_taker_id` is required to read or acknowledge results.** Both endpoints take it as a query parameter; it first appears on the `results.kit_activated` webhook.
- **Send an `Idempotency-Key` header on order creation** (unique per attempt) – retries with the same key + body replay the original result instead of double-ordering; same key + different body → `409`. Without the header there is NO server-side dedupe (`client_order_reference_id` is reconciliation-only): on ambiguous failure, list recent orders and match your reference before retrying.
- **Acknowledgment gates patient access and shifts liability.** Must be an explicit end-user action.
- **Track by `order_item_id`, not `order_id`.** Results, activation, and acknowledgment are all per item.
- **Presigned result URLs expire in 15 minutes.** Re-fetch to re-mint rather than caching the URL.
- **Collection type dictates the address field.** `shipping_address` for `AT_HOME`, `requested_lab_address` for `GO_TO_LAB`.
- **Catalog curation blocks ordering.** `PUT /api/v1/products/selection` is a full-set write; curated-out products vanish from reads and ordering one returns `403`.
- **Sandbox and production hold separate webhook URLs**, and access is enabled per environment (sandbox first).

## Live Documentation

Append `.md` to any page URL for markdown. If the terra-docs MCP server (`https://docs.tryterra.co/~gitbook/mcp`) is connected, use its tools to search and fetch these pages instead.

- [What is Vantage API?](https://docs.tryterra.co/vantage-api/overview)
- [Account setup and authentication](https://docs.tryterra.co/vantage-api/account-setup-and-api-keys)
- [Core concepts](https://docs.tryterra.co/vantage-api/core-concepts)
- [Ordering your first test](https://docs.tryterra.co/vantage-api/ordering-your-first-test)
- [Working with Sandbox](https://docs.tryterra.co/vantage-api/working-with-sandbox)
- [Webhooks](https://docs.tryterra.co/vantage-api/webhooks)
- [Managing orders](https://docs.tryterra.co/vantage-api/managing-orders)
- [Test Collection Methods](https://docs.tryterra.co/vantage-api/test-collection-methods)
- [Results](https://docs.tryterra.co/vantage-api/results)
- [Errors](https://docs.tryterra.co/vantage-api/errors)
- [Monitoring and debugging](https://docs.tryterra.co/vantage-api/monitoring)
- [Best practices](https://docs.tryterra.co/vantage-api/best-practices)
- [Acknowledging Results](https://docs.tryterra.co/vantage-api/acknowledging-results)
- [Going to production](https://docs.tryterra.co/vantage-api/going-to-production)
- API reference: [activation](https://docs.tryterra.co/reference/vantage-api/activation), [clients](https://docs.tryterra.co/reference/vantage-api/clients), [orders](https://docs.tryterra.co/reference/vantage-api/orders), [products](https://docs.tryterra.co/reference/vantage-api/products), [results](https://docs.tryterra.co/reference/vantage-api/results)

