VitaRelay Developers

Partner API Reference

Pull the research-use-only (RUO) catalog and submit orders from your own storefront. VitaRelay charges your card on file at the platform price; your retail price is entirely yours to set.

Base URL https://vitarelay.com/api/public/partner/v1

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.

Request header
Authorization: Bearer vr_live_5f3c...

Scopes. Each key carries an explicit scope list. A request without the required scope returns 403 insufficient_scope.

NameTypeNotes
products:readscopeGET /catalog and GET /catalog/{id}
orders:writescopePOST /orders
orders:readscopeGET /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 seconds

Exceeding 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

GET/catalogscope: products:read

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.

NameTypeNotes
pageintegerQuery. Default 1. Must be ≥ 1.
limitintegerQuery. Default 25, maximum 100.
categorystringQuery. Exact category slug match (see category on a product).
qstringQuery. Keyword search on name and keywords. Max 200 chars.
Request
curl "https://vitarelay.com/api/public/partner/v1/catalog?page=1&limit=2&q=semaglutide" \
  -H "Authorization: Bearer vr_live_..."
200 OK
{
  "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

GET/catalog/{id}scope: products:read

{id} accepts either the product id (UUID) or its slug. The object is identical to a list entry.

200 OK
{
  "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

POST/ordersscope: orders:write

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.

NameTypeNotes
itemsarrayRequired. 1–50 lines of { product_id, quantity }.
items[].product_idstringRequired. Must be an eligible catalog product id.
items[].quantityintegerRequired. 1–999.
shipping_addressobjectRequired.
shipping_address.namestringRequired. Split into first/last for the label.
shipping_address.line1stringRequired.
shipping_address.line2stringOptional.
shipping_address.citystringRequired.
shipping_address.statestringRequired.
shipping_address.postal_codestringRequired.
shipping_address.countrystringRequired. Normalized to a 2-letter uppercase code (default US).
customerobjectOptional. { email?, phone? }. Email must be well-formed.
referencestringOptional. Your own order reference, ≤ 128 chars. Echoed back as reference.
idempotency_keystringOptional. ≤ 128 chars. See Idempotency.
Request
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"
  }'
201 Created
{
  "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"
  }
}
200 OK — idempotent replay
{
  "data": { "id": "b0d0b2a1-...", "order_number": "VR-104233", "...": "..." },
  "idempotent_replay": true
}
402 — card declined (the order still exists)
{
  "data": {
    "id": "b0d0b2a1-...",
    "payment_status": "unpaid",
    "...": "..."
  },
  "payment_failed": true,
  "payment_message": "Payment was declined"
}
402 — no card on file
{
  "error": {
    "code": "no_payment_method",
    "message": "No card on file for this organization. Add a default card in Settings → Billing before submitting orders."
  }
}
422 — product unavailable
{
  "error": {
    "code": "product_unavailable",
    "message": "Unavailable product_id(s): 11111111-2222-3333-4444-555555555555"
  }
}
400 — validation failed
{
  "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

GET/orders/{id}scope: orders:read

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.

200 OK
{
  "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 }.

NameTypeNotes
unauthorized401Missing/malformed Authorization header, or invalid, revoked or expired key.
insufficient_scope403The key lacks the scope the endpoint requires.
bad_request400Invalid query parameter or path identifier.
invalid_body400Request body was not valid JSON.
validation_failed400One or more body fields are invalid; see fields[].
test_key_live_gateway400A test key attempted an order while the payment gateway is in production.
not_found404Product not found/ineligible, or order not visible to your organization.
no_payment_method402No default card on file for your organization.
product_unavailable422One or more product_ids are not in the eligible RUO catalog.
order_rejected422The order engine rejected the order; the message explains why.
rate_limited429Per-key rate limit exceeded. See Retry-After.
internal_error500Unexpected 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.

NameTypeNotes
order.paideventPayment captured for the order.
order.payment_failedeventThe card on file was declined; the order exists as unpaid.
order.fulfilledeventFulfillment marked fulfilled.
order.shippedeventTracking number assigned or fulfillment marked shipped.
order.deliveredeventFulfillment marked delivered.
order.cancelledeventOrder 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}:

POST to your endpoint
{
  "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:

Node.js verification
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.