About terra-vantage
A skill for building integrations with Terra API Vantage, which manages blood and DNA diagnostic products, orders, logistics, and results. It guides developers through catalog-to-results workflows, webhooks, activation, and mandatory result acknowledgment, while distinguishing API operations from account administration via CLI or MCP.
Key features
- 01Product catalog and variant selection
- 02Order and collection-type workflows
- 03Webhook setup and signature verification
- 04Kit activation and result retrieval
- 05Mandatory user-triggered result acknowledgment
- 06Credential and API-version administration
Use cases
- 01Embedding diagnostic test ordering in an application
- 02Routing at-home kits or lab-draw orders
- 03Handling fulfillment and result-status webhooks
- 04Retrieving and acknowledging test results
MCP support
The skill describes Terra API admin MCP tools for account administration, such as retrieving credentials and managing API versions. These tools do not call Vantage product, order, or result endpoints; those remain part of the integrating client's API calls.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ tryterra/ agent-skills --skill terra-vantageAfter installing, type/terra-vantagein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
metadata.jsonREADME.mdreferences/api-reference.mdresults.mdwebhooks.mdDetails
From the project READMETerra 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). 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:
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 do not call the Vantage order, product or result endpoints: those stay in your own client, and terra data-api has no Vantage paths. What they do is supply Vantage's credentials and set the environment's Vantage API version.
Credentials. Vantage authenticates with the same dev-id and API key as every other Terra API product:
terra environments list # which dev-ids exist
terra environments retrieve-api-key --env <dev-id> # 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):
terra vantage api-version retrieve --env <dev-id> # pinned, latest, and every version you can move to
terra vantage api-version update --env <dev-id> --version <date> --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 for what each version changes.
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:
- HTTP Basic (what the docs show): username =
dev-id, password = API key, i.e.Authorization: Basic base64(DEV_ID:API_KEY). Older docs called theseCLIENT_ID/CLIENT_SECRET– same values. - 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 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_bydeadline; 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; 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. 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}/simulatewith{"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 (422otherwise);results_readyalso stores a sample result so download/acknowledge work end to end. Returns403in 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}(thesupplier_item_idarrives 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, andevent_idin order responses and webhooks are 64-bit snowflakes serialized as strings – never parse them as numbers (JavaScript corrupts them). Catalog reads (/products*) still return numericidfields; order requests takevariant_idas 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 sameresults.*statuses: webhook statuses match REST reads verbatim. (Historic: webhooks once saidfulfillment.*and REST saidorder.failed; both retired.) - Signature timestamp is Unix SECONDS.
X-Terra-Signature: t=<unix_seconds>,v1=<hex>; treatingtas milliseconds makes every verification fail. Sign-check against the raw body. test_taker_idis required to read or acknowledge results. Both endpoints take it as a query parameter; it first appears on theresults.kit_activatedwebhook.- Send an
Idempotency-Keyheader 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_idis 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/selectionis a full-set write; curated-out products vanish from reads and ordering one returns403. - 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-Versionon a request changes only that response; webhook deliveries always use the environment's pin. Check it withterra vantage api-version retrievebefore writing a parser, and store each delivery'sX-Terra-Versionheader with its body.
Live Documentation
references/api-reference.md maps each endpoint to its reference page. The guides worth reading in full:
- What is Vantage API?
- Account setup and authentication
- Versioning
- Working with Sandbox
- Acknowledging Results
- Best practices
- 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.