Overview
The Partner API lets an approved VitaRelay partner (provider organization or Vita Rep) read the RUO product catalog and submit orders programmatically. Every request is authenticated with an organization-bound API key; every order is attributed to that organization and charged to its card on file at the VitaRelay platform price.
All products exposed through this API are research use only. They are not drugs, are not for human or veterinary use, and must not be marketed as such.
The API is versioned in the path (/v1). Responses are JSON. All money is expressed in integer cents (USD).
Authentication
Pass your key as a bearer token on every request. Keys look like vr_live_<48 hex chars> or vr_test_<48 hex chars> and are issued by a VitaRelay admin and bound to a single organization. VitaRelay stores only a SHA-256 hash — the raw key is shown once at issuance and cannot be recovered.
Authorization: Bearer vr_live_5f3c...Scopes. Each key carries an explicit scope list. A request without the required scope returns 403 insufficient_scope.
| Name | Type | Notes |
|---|---|---|
| products:read | scope | GET /catalog and GET /catalog/{id} |
| orders:write | scope | POST /orders |
| orders:read | scope | GET /orders/{id} |
Environments. A key is either live or test. Catalog reads behave identically for both. Order submission with a test key is rejected with 400 test_key_live_gateway whenever the VitaRelay payment gateway is running in production — a test key can never create a real charge. Use a live key for real orders.
Getting a key. Keys are admin-issued for the MVP: contact VitaRelay and we will provision a key (and the scopes you need) for your organization. Before you can submit orders, your organization must have a default card on file under Settings → Billing in the VitaRelay portal; without one POST /orders returns 402 no_payment_method.
Rate limits
The default limit is 120 requests per minute per key. VitaRelay can raise or lower this per key. Every successful response carries the current window:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 117
X-RateLimit-Reset: 1755734400 # unix secondsExceeding the limit returns 429 rate_limited with Retry-After (seconds) plus the same X-RateLimit-* headers. The limiter fails open: if the counter backend is unavailable your request is allowed through and the X-RateLimit-* headers are omitted.
List catalog products
Returns the eligible RUO product set — published research products with a price, at least one image and a real description. Cost, pharmacy and margin data are never returned.
| Name | Type | Notes |
|---|---|---|
| page | integer | Query. Default 1. Must be ≥ 1. |
| limit | integer | Query. Default 25, maximum 100. |
| category | string | Query. Exact category slug match (see category on a product). |
| q | string | Query. Keyword search on name and keywords. Max 200 chars. |
curl "https://vitarelay.com/api/public/partner/v1/catalog?page=1&limit=2&q=semaglutide" \
-H "Authorization: Bearer vr_live_..."{
"data": [
{
"id": "7c1f0f7e-9a2b-4f1d-9c33-1a2b3c4d5e6f",
"slug": "semaglutide-5mg-vial",
"name": "Semaglutide 5mg Vial",
"description": "Lyophilized research peptide, 5mg per vial ...",
"category": "weight_loss_metabolic",
"category_label": "Weight Loss & Metabolic",
"image_urls": ["https://cdn.vitarelay.com/products/sema-5mg.jpg"],
"dosage_form": "vial",
"strengths": ["5 mg"],
"volume": "5 mL",
"price_cents": 12900
}
],
"page": 1,
"limit": 2,
"total": 37
}strengths is an array of formatted strings (placeholder zero strengths are filtered out) and volume is a formatted string or null. slug, category, category_label and dosage_form may be null. total is the count after filtering, not the page size.
Retrieve one catalog product
{id} accepts either the product id (UUID) or its slug. The object is identical to a list entry.
{
"data": {
"id": "7c1f0f7e-9a2b-4f1d-9c33-1a2b3c4d5e6f",
"slug": "semaglutide-5mg-vial",
"name": "Semaglutide 5mg Vial",
"description": "Lyophilized research peptide, 5mg per vial ...",
"category": "weight_loss_metabolic",
"category_label": "Weight Loss & Metabolic",
"image_urls": ["https://cdn.vitarelay.com/products/sema-5mg.jpg"],
"dosage_form": "vial",
"strengths": ["5 mg"],
"volume": "5 mL",
"price_cents": 12900
}
}An unknown or ineligible identifier returns 404 not_found. An identifier longer than 200 characters returns 400 bad_request.
Submit an order
Creates an order attributed to your organization and charges your card on file synchronously. Prices are always re-read from the VitaRelay catalog — any price sent in the body is ignored.
Availability. Order submission may be temporarily disabled by VitaRelay while charge testing is completed. If submission is disabled, the endpoint returns HTTP 403 with code orders_disabled and no order is created or charged. Catalog reads and order status lookups remain available at all times.
| Name | Type | Notes |
|---|---|---|
| items | array | Required. 1–50 lines of { product_id, quantity }. |
| items[].product_id | string | Required. Must be an eligible catalog product id. |
| items[].quantity | integer | Required. 1–999. |
| shipping_address | object | Required. |
| shipping_address.name | string | Required. Split into first/last for the label. |
| shipping_address.line1 | string | Required. |
| shipping_address.line2 | string | Optional. |
| shipping_address.city | string | Required. |
| shipping_address.state | string | Required. |
| shipping_address.postal_code | string | Required. |
| shipping_address.country | string | Required. Normalized to a 2-letter uppercase code (default US). |
| customer | object | Optional. { email?, phone? }. Email must be well-formed. |
| reference | string | Optional. Your own order reference, ≤ 128 chars. Echoed back as reference. |
| idempotency_key | string | Optional. ≤ 128 chars. See Idempotency. |
curl -X POST "https://vitarelay.com/api/public/partner/v1/orders" \
-H "Authorization: Bearer vr_live_..." \
-H "Content-Type: application/json" \
-d '{
"items": [{ "product_id": "7c1f0f7e-9a2b-4f1d-9c33-1a2b3c4d5e6f", "quantity": 2 }],
"shipping_address": {
"name": "Jordan Reyes",
"line1": "44 Lab Way",
"line2": "Suite 300",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"country": "US"
},
"customer": { "email": "jordan@example.com", "phone": "5125550134" },
"reference": "SHOP-10241",
"idempotency_key": "SHOP-10241"
}'{
"data": {
"id": "b0d0b2a1-8e7f-4a11-9c2a-77c1d1f8c0aa",
"order_number": "VR-104233",
"status": "pending",
"payment_status": "paid",
"reference": "SHOP-10241",
"total_cents": 25800,
"currency": "USD",
"items": [
{
"product_id": "7c1f0f7e-9a2b-4f1d-9c33-1a2b3c4d5e6f",
"quantity": 2,
"unit_price_cents": 12900
}
],
"created_at": "2026-08-21T00:04:11.882Z"
}
}{
"data": { "id": "b0d0b2a1-...", "order_number": "VR-104233", "...": "..." },
"idempotent_replay": true
}{
"data": {
"id": "b0d0b2a1-...",
"payment_status": "unpaid",
"...": "..."
},
"payment_failed": true,
"payment_message": "Payment was declined"
}{
"error": {
"code": "no_payment_method",
"message": "No card on file for this organization. Add a default card in Settings → Billing before submitting orders."
}
}{
"error": {
"code": "product_unavailable",
"message": "Unavailable product_id(s): 11111111-2222-3333-4444-555555555555"
}
}{
"error": {
"code": "validation_failed",
"message": "One or more fields are invalid",
"fields": [
{ "field": "items[0].quantity", "message": "quantity must be an integer between 1 and 999" },
{ "field": "shipping_address.state", "message": "state is required" }
]
}
}A declined card returns HTTP 402 but the order is not discarded: it is created, marked unpaid, flagged for VitaRelay staff, and returned in the response body. An order.payment_failed webhook is emitted if you have an endpoint subscribed.
Retrieve order status
Returns the order only if it belongs to your organization. Anything else — another org's order, an unknown id, or a malformed UUID — returns 404 not_found.
{
"data": {
"id": "b0d0b2a1-8e7f-4a11-9c2a-77c1d1f8c0aa",
"order_number": "VR-104233",
"status": "shipped",
"payment_status": "paid",
"reference": "SHOP-10241",
"total_cents": 25800,
"currency": "USD",
"items": [
{ "product_id": "7c1f0f7e-...", "quantity": 2, "unit_price_cents": 12900 }
],
"created_at": "2026-08-21T00:04:11.882Z",
"fulfillment": {
"status": "shipped",
"tracking_number": "794657123456",
"tracking_carrier": "FEDEX",
"tracking_url": "https://www.fedex.com/fedextrack/?trknbr=794657123456",
"shipped_at": "2026-08-22T15:02:00.000Z"
}
}
}Every fulfillment field can be null until the order ships. Poll this endpoint or, preferably, subscribe to webhooks.
Pricing & billing
price_cents on a catalog product is the VitaRelay platform price — what VitaRelay charges you. It is authoritative and cannot be overridden through the API; order lines are always re-priced server-side from the catalog at submit time.
You set your own retail price on your storefront and keep the difference. VitaRelay charges your organization's default card on file synchronously during POST /orders. If the charge is declined the order is created as unpaid and VitaRelay contacts you to collect payment before fulfillment.
No VitaRelay rep commission is earned on Partner API orders — affiliate attribution and commission are explicitly zeroed.
Idempotency
Send idempotency_key on POST /orders to make retries safe. Keys are scoped to your organization. If an order already exists for that key, VitaRelay returns the original order with HTTP 200 and "idempotent_replay": true, and no second charge is attempted. The replay check runs before any charge, so a network timeout on your side can always be retried with the same key.
Reuse your own order id (e.g. SHOP-10241) as the key. Maximum 128 characters.
Errors
Every failure uses the same envelope:
{ "error": { "code": "insufficient_scope", "message": "Key is missing required scope: orders:write" } }validation_failed additionally includes a fields array of { field, message }.
| Name | Type | Notes |
|---|---|---|
| unauthorized | 401 | Missing/malformed Authorization header, or invalid, revoked or expired key. |
| insufficient_scope | 403 | The key lacks the scope the endpoint requires. |
| bad_request | 400 | Invalid query parameter or path identifier. |
| invalid_body | 400 | Request body was not valid JSON. |
| validation_failed | 400 | One or more body fields are invalid; see fields[]. |
| test_key_live_gateway | 400 | A test key attempted an order while the payment gateway is in production. |
| not_found | 404 | Product not found/ineligible, or order not visible to your organization. |
| no_payment_method | 402 | No default card on file for your organization. |
| product_unavailable | 422 | One or more product_ids are not in the eligible RUO catalog. |
| order_rejected | 422 | The order engine rejected the order; the message explains why. |
| rate_limited | 429 | Per-key rate limit exceeded. See Retry-After. |
| internal_error | 500 | Unexpected server error. Safe to retry with the same idempotency_key. |
Note that a declined card is not an error envelope: it returns HTTP 402 with the created order plus payment_failed: true and payment_message.
Webhooks
Webhook endpoints are registered per organization by a VitaRelay admin (one active endpoint per org, HTTPS only) along with the list of events you want. The signing secret is shown once at registration and can be rotated.
| Name | Type | Notes |
|---|---|---|
| order.paid | event | Payment captured for the order. |
| order.payment_failed | event | The card on file was declined; the order exists as unpaid. |
| order.fulfilled | event | Fulfillment marked fulfilled. |
| order.shipped | event | Tracking number assigned or fulfillment marked shipped. |
| order.delivered | event | Fulfillment marked delivered. |
| order.cancelled | event | Order status moved to cancelled. |
Each event is emitted at most once per (order, event type). The payload carries the same whitelisted order object as GET /orders/{id}:
{
"id": "0f0f2f0a-6a8b-4c66-9f0f-9c9a2f2b1c33",
"type": "order.shipped",
"api_version": "2026-08-20",
"created_at": "2026-08-22T15:02:03.114Z",
"data": {
"order": {
"id": "b0d0b2a1-8e7f-4a11-9c2a-77c1d1f8c0aa",
"order_number": "VR-104233",
"status": "shipped",
"payment_status": "paid",
"reference": "SHOP-10241",
"total_cents": 25800,
"currency": "USD",
"items": [
{ "product_id": "7c1f0f7e-...", "quantity": 2, "unit_price_cents": 12900 }
],
"created_at": "2026-08-21T00:04:11.882Z",
"fulfillment": {
"status": "shipped",
"tracking_number": "794657123456",
"tracking_carrier": "FEDEX",
"tracking_url": "https://www.fedex.com/fedextrack/?trknbr=794657123456",
"shipped_at": "2026-08-22T15:02:00.000Z"
}
}
}
}Delivery headers:
X-VitaRelay-Event: order.shipped
X-VitaRelay-Delivery: 0f0f2f0a-6a8b-4c66-9f0f-9c9a2f2b1c33
X-VitaRelay-Timestamp: 2026-08-22T15:02:03Z
X-VitaRelay-Signature: sha256=<hex HMAC-SHA256 of the raw request body>The signature is an HMAC-SHA256 of the exact raw request body, keyed with your endpoint secret, hex-encoded and prefixed with sha256=. Verify against the raw body before parsing it, and compare in constant time:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
const a = Buffer.from(header ?? "");
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Retries. Respond 2xx quickly. Any non-2xx response or network failure is retried with exponential backoff of 3^attempt minutes capped at 4 hours (≈ 3m, 9m, 27m, 81m, 4h) for up to 6 attempts, after which the delivery is marked dead. Use X-VitaRelay-Delivery to de-duplicate.
RUO compliance
All products available through the Partner API are research use only. They are not approved drugs and are not for human or veterinary consumption.
You are responsible, under your partner agreement, for age verification (21+), for your own storefront claims and marketing, and for compliance with all applicable federal, state and local law in the jurisdictions you ship to. VitaRelay may suspend keys for non-compliant use.
Questions or key requests: contact your VitaRelay account manager.