--- name: terra-vantage description: Order at-home and go-to-lab diagnostic tests and deliver results with Terra API Vantage. 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 API dev-id and 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. license: MIT compatibility: Requires network access to docs.tryterra.co for current endpoint schemas metadata: author: terra version: "1.3.0" --- # Terra API Vantage 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. **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. ## Start from an example app For a new app, start from the published example for a diagnostics storefront and operations console, 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: ```sh terra examples list --select name,title,description terra examples clone vantage-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/vantage-web-app. ## Account tools: CLI or MCP The account tools configure and inspect Vantage; they never place, activate or acknowledge an order. Those calls stay in your own client against the Vantage hosts below, and `terra data-api` has no Vantage paths. What the tools do is supply Vantage's credentials, set the environment's API version, markets, product selection and webhook URL, and read its orders, results and webhook events for debugging. **Credentials.** Vantage authenticates with the same dev-id and API key as every other Terra API product: ```sh terra environments list # which dev-ids exist terra environments retrieve-api-key --env # dev-id and API key for Basic auth ``` 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. `retrieve-api-key` 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 `--select` to take only the field you need. Over MCP, `environments_read` with `method: "retrieve_api_key"` returns the same pair into the conversation, so prefer the CLI when the value only has to reach a file. **API version.** Vantage response shapes are versioned by date, and the environment's pinned version decides the shape of every request that sends no `X-Terra-Version` header and of **every webhook delivery** (the header never applies to webhooks): ```sh terra vantage api-version retrieve --env # pinned, latest, and every version you can move to terra vantage api-version update --env --version --dry-run # preview the change ``` An update takes effect on the environment's next Vantage request, older or newer than the current pin, and there is no unpin. Because it changes the shape your webhook handler receives, move the pin only once the handler accepts both shapes, telling them apart by the `X-Terra-Version` header each delivery carries. Read [Versioning](https://docs.tryterra.co/vantage-api/versioning.md) for what each version changes. **Catalogue, webhook and debugging.** Every `terra vantage` command acts on the deployment the environment's production setting selects (sandbox for a testing environment), so pass the right `--env` rather than a host: ```sh terra vantage catalogue list --env # every product and the markets it is sold in terra vantage products list --env # the products this environment lists and orders terra vantage products disable --env --dry-run # preview curating one out; enable is the inverse terra vantage webhook update --env --url https://... --dry-run # where Vantage events are delivered terra vantage events list --env # what was sent, with every delivery attempt terra vantage events generate --env --event-type order.status_changed # a signed test event to the current URL terra vantage orders list --env --client-order-reference-id terra vantage results list --env --status results.results_ready --is-acknowledged=false ``` `products enable`/`disable` change one product and `batch-enable`/`batch-disable` up to 100 at once; they edit the same selection as `PUT /api/v1/products/selection`, without its full-set replace. `events resend --event-id ` sends a past event again to the current URL, so the endpoint can receive it twice. Run `terra reference vantage` for every command and filter. Over MCP the same commands are the `vantage_*` tools, such as `vantage_orders_read` with `method: "list"`. 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. ## Authentication Vantage uses your standard **Terra API credentials** (the `dev-id` and API key from the Terra API 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: ` + `x-api-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=,v1=`; 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. - **Presigned result URLs expire in 15 minutes.** Re-fetch to re-mint rather than caching the URL. - **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). - **The pinned API version shapes webhooks, and the request header cannot override it there.** `X-Terra-Version` on a request changes only that response; webhook deliveries always use the environment's pin. Check it with `terra vantage api-version retrieve` before writing a parser, and store each delivery's `X-Terra-Version` header with its body. ## Live Documentation [references/api-reference.md](references/api-reference.md) maps each endpoint to its reference page. The guides worth reading in full: - [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) - [Versioning](https://docs.tryterra.co/vantage-api/versioning) - [Working with Sandbox](https://docs.tryterra.co/vantage-api/working-with-sandbox) - [Acknowledging Results](https://docs.tryterra.co/vantage-api/acknowledging-results) - [Best practices](https://docs.tryterra.co/vantage-api/best-practices) - [Going to production](https://docs.tryterra.co/vantage-api/going-to-production) 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.