Core concepts
The Vantage API data model: catalog structure, orders and order items, recipients and test takers, status lifecycles, and conventions used across all endpoints.
The catalog: product types → products → variants
The catalog is three levels deep:
Product type
The broadest category
Blood Test, DNA Test
Product
An offering within a type
An at-home blood test kit from a given supplier
Variant
The exact configuration an end user receives - the thing you order
The "Inflammation" panel (hs-CRP + Vitamin D), 3-5 day turnaround
You browse with GET /api/v1/products (types) → GET /api/v1/products/{product_type_id} (products) → GET /api/v1/products/{product_id}/variants (variants), and you order a variant_id. A variant advertises which collection methods it supports in available_collection_types (an array of strings: "AT_HOME", "GO_TO_LAB").
Your catalog is scoped to your contract: browsing returns only the panels and fulfilment options your account is entitled to, and orders for anything outside it are rejected. To add panels to your contract, contact the Terra team. Within your entitlements, you can additionally curate which products your catalog reads return - see Managing orders.
Orders and order items
An order is one purchase: it has a recipient, a collection type, an address, and one or more order items (one per variant_id + quantity). Everything result-related happens per order item: kit activation, result statuses, result downloads, and acknowledgment are all keyed by order_item_id, not order_id.
client_order_reference_id- your unique identifier for the order, supplied when you create it. Use it to reconcile webhooks and REST reads with your own system. For retry-safe creation, send anIdempotency-Keyheader - the reference id alone does not deduplicate.order_id,order_item_id,recipient_id,test_taker_id- Terra-generated identifiers.
Treat IDs as strings. All Vantage identifiers are 64-bit values serialized as JSON strings (e.g. "251285377984405504"). Parsing them as floating-point numbers (the default in JavaScript) silently corrupts them. Store and compare them as strings.
Recipient vs test taker
The recipient is the person the order is placed for - name, date of birth, gender at birth, email, phone. You provide it at order time.
The test taker is the person who actually takes the test. For suppliers with kit activation, the test taker registers themselves during activation (and may differ from the recipient, supplier-permitting). Terra assigns a
test_taker_idat that point and includes it in every result webhook.
You need the test_taker_id to fetch and acknowledge results - capture it from the result webhooks (or from GET /api/v1/orders/{orderID}).
Collection types
Every order has a collection_type that determines how the sample is collected and which address field the order requires:
collection_type
Meaning
Address field
AT_HOME
Kit shipped to the recipient, who self-collects
shipping_address
GO_TO_LAB
Sample drawn at a lab draw site (Patient Service Center)
requested_lab_address
Status lifecycles
An order carries a fulfilment status; each order item carries a results status.
Order (fulfilment) statuses, as returned by the REST API:
New orders start at order.payment_processing.
Order item (results) statuses:
REST endpoints and webhook payloads use the same order.* vocabulary for fulfilment statuses (and the same results.* vocabulary for results statuses) - a status seen on a webhook can be matched verbatim against any REST read of the order.
Escalation levels
Results carrying clinical escalation include an escalation_level, one of (in ascending severity):
not_escalated, very_low, low, medium, high, very_high
Escalated results also carry an acknowledgment_due_by timestamp - see Acknowledging Results.
Conventions
Money is integer cents (
price_cents,total_cents); currency is an ISO 4217 numeric code (840= USD,978= EUR,826= GBP).Phone numbers are a single E.164 string everywhere — the industry-standard format used by Stripe, Twilio and FHIR. Send
phone_numberas a+-prefixed E.164 string ("+14155551234"); reads and webhooktest_takerpayloads return the same string. There is no separate recipientcountry_codefield.Timestamps in webhook payloads (
timestamp) are Unix seconds; REST timestamps are RFC 3339 strings.Errors follow RFC 7807 problem-detail format - see Errors.
List endpoints (
GET /api/v1/orders,GET /api/v1/results,GET /api/v1/webhook-deliveries) are keyset-paginated: pass the returnednext_cursoras?cursor=for the next page; a page withoutnext_cursoris the last one.
Last updated
Was this helpful?