> ## Documentation Index
> Fetch the complete documentation index at: https://api.vitarelay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Partner API

> Grow your network and sell to your own customers from your own software: invite clinics, practices, pharmacies and reps, and run a research-product store through VitaRelay fulfillment.

## What is the Partner API?

The Partner API is for **rep organizations** — a Vita Rep, or a dual Rep + Clinic account — that recruit clinics and other reps. It does what the **Refer a Tele Clinic** form in the app does, but from your own system: create an invite, get the signup link back, check whether it was accepted, and revoke it.

It is separate from the VitaClinic API, which is for a clinic placing orders and managing its patients. Every endpoint here lives only at `/api/public/partner/v1`: nothing is duplicated in the VitaClinic API.

<Info>
  Base URL: `https://vitarelay.com/api/public/partner/v1`. The old `/api/public/rep/v1` path, and the invite paths that used to be under `/api/public/v1`, redirect here (307). Any other `/api/public/partner/v1/...` path still redirects to the VitaClinic API at `/api/public/v1`. Authenticate with a Bearer API key. Test keys (`vr_test_…`) run in a sandbox: nothing is created and no email is sent.
</Info>

## How it works

<Steps>
  <Step title="Get an API key">
    Ask VitaRelay for a key with **`invites:write`** (to create and revoke invites) and **`invites:read`** (to list and fetch them). See [API keys](/getting-started/api-keys). Your organization must be a rep or a dual Rep + Clinic account; any other organization gets `403 not_available`.
  </Step>

  <Step title="Create an invite">
    `POST /invites` with the prospect's details. The response includes an `invite_url`, which opens the signup page for that invite on **your own branded domain** when you have one. By default the invite is also emailed to the prospect.
  </Step>

  <Step title="The prospect signs up">
    They open the link and register as the kind of organization you invited (Tele Clinic, practice, pharmacy or Rep Partner). They join your network, and you earn commission on their orders as usual.
  </Step>

  <Step title="Track it">
    `GET /invites` or `GET /invites/{id}` shows whether each invite is `pending`, `accepted` or `expired`. `POST /invites/{id}/revoke` cancels one that has not been used.
  </Step>
</Steps>

## Create an invite

```bash theme={null}
curl -X POST https://vitarelay.com/api/public/partner/v1/invites \
  -H "Authorization: Bearer $VITARELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "clinic",
    "email": "owner@jasonmeds.com",
    "phone": "(229) 719-4222",
    "first_name": "Jason",
    "last_name": "Tests",
    "product_types": ["503a", "rnd", "supplements"],
    "external_invite_id": "crm-lead-4471"
  }'
```

```json theme={null}
{
  "data": {
    "id": "9c7f2f1e-3b0a-4f4e-9d0b-1c2d3e4f5a6b",
    "type": "clinic",
    "status": "pending",
    "invite_code": "k3j9x2…",
    "invite_url": "https://portal.example.com/register/vita-clinic?invite=k3j9x2…",
    "email": "owner@jasonmeds.com",
    "phone": "(229) 719-4222",
    "first_name": "Jason",
    "last_name": "Tests",
    "product_types": ["503a", "rnd", "supplements"],
    "external_invite_id": "crm-lead-4471",
    "expires_at": "2026-10-07T17:00:00Z",
    "created_at": "2026-10-02T17:00:00Z"
  }
}
```

| Field | Notes |
| - | - |
| `type` | Who you are inviting: `clinic` (a Tele Clinic / Vita Clinic, the default), `practice` (a clinic or provider practice with its own prescribers), `pharmacy` (a partner pharmacy) or `rep` (another Vita Rep). These are the same four options as the invite form in the app. |
| `email` | Required unless `send_email` is `false`. The invite email is sent here. |
| `phone`, `first_name`, `last_name` | Optional, but provide at least one of `email`, `phone` or `first_name`. |
| `product_types` | Which product groups the invited organization can see: any of `503a`, `503b`, `rnd`, `supplements`. Omit for all. `503b` is only for `practice` and `pharmacy` invites; sending it with `clinic` or `rep` returns `422 validation_failed`. |
| `send_email` | `true` by default. Set `false` to get the link back **without** emailing the prospect, for example to send it yourself. |
| `external_invite_id` | Your own id for the invite. Makes the call **safe to retry** (see below). |

<Note>
  Invites created through the API use your standard commission pricing. Setting your own per-clinic pricing is done in the dashboard. An invite link is valid for **5 days**; after that its `status` is `expired` and you can create a new one.
</Note>

## Retries are safe

Send an `external_invite_id` and repeat the same request after a timeout: you get the **same invite back** (`200`), not a second one. Reusing an `external_invite_id` with a **different** body returns `409 duplicate_request`. A request that failed creates nothing, so its id can be reused.

## List, fetch and revoke

* `GET /invites` — your invites, newest first. Filter with `?type=clinic|practice|pharmacy|rep` and `?status=pending|accepted|expired`, page with `?limit=` (max 100) and `?cursor=` (use `next_cursor` from the previous page).
* `GET /invites/{id}` — one invite.
* `POST /invites/{id}/revoke` — cancels a pending invite so the link stops working. Revoking an invite that is already revoked is fine and returns it unchanged. An **accepted** invite cannot be revoked (`409 invite_already_accepted`).

An invite you did not create — or that does not exist — returns `404 invite_not_found`; you can never see another organization's invites.

## Access and scopes

| Scope | Grants |
| - | - |
| `invites:read` | `GET /invites`, `GET /invites/{id}` |
| `invites:write` | `POST /invites`, `POST /invites/{id}/revoke` (also allows the reads) |
| `orders:write` | `POST /wholesale/orders` |
| `orders:read` | `GET /wholesale/orders`, `GET /wholesale/orders/{id}` |
| `products:read` | `GET /wholesale/catalog` |
| `store:read` | `GET /store/catalog`, `GET /store/orders`, `GET /store/orders/{id}` |
| `store:write` | `PUT /store/products/{product_id}`, `POST /store/orders`, `POST /store/orders/{id}/pay`, `GET /store/checkout-config` (also allows the reads) |

## Errors

Errors use the same shape as the rest of the platform: `{ "error": { "code": "…", "message": "…" } }`.

| Status | Code | When |
| - | - | - |
| 400 | `invalid_body` | The body is not valid JSON. |
| 401 | `unauthorized` | Missing, invalid or revoked API key. |
| 403 | `insufficient_scope` | The key lacks `invites:read` or `invites:write`. |
| 403 | `not_available` | Your organization is not a rep or dual Rep + Clinic account. |
| 404 | `invite_not_found` | No invite with that id belongs to your organization. |
| 409 | `duplicate_request` | `external_invite_id` reused with a different body, or the same request is still in flight. |
| 409 | `invite_already_accepted` | You tried to revoke an invite that has already been accepted. |
| 422 | `validation_failed` | A field is missing or invalid (the message says which). |
| 429 | `rate_limited` | Too many requests; retry after the `Retry-After` header. |
| 500 | `internal_error` | Something went wrong on our side. Nothing was created; retrying with the same `external_invite_id` is safe. |

## Order for your customers at wholesale

The simplest way to sell research-use (R\&D) products: read VitaRelay's wholesale catalog, show **your own prices** on your own page, collect payment from your customer yourself, and post the order to us. It ships to your customer, and VitaRelay charges **your card on file** at wholesale. No patient, customer record or invite is created.

<Steps>
  <Step title="Read the wholesale catalog">
    `GET /wholesale/catalog` lists every research product you can order with `wholesale_price_cents` (what your card is charged) and the shipping methods. It uses the same keys you already have: `products:read`.
  </Step>

  <Step title="Show your price, take the customer's payment">
    Your page shows your own price and takes payment however you like. That is between you and your customer.
  </Step>

  <Step title="Post the order">
    `POST /wholesale/orders` with `items`, `ship_to` (name, phone, address, optional email), the buyer's `attestation` (21+, research use only, terms accepted) and your `external_order_id`. Prices come from our catalog, never from the request. VitaRelay charges your default card and the response has `payment.status`. A declined card leaves the order unpaid and nothing ships. No card on file returns `402 no_payment_method` before an order is created.
  </Step>

  <Step title="Track it">
    `GET /wholesale/orders/{id}` for status and tracking (`tracking.number`, `carrier`, `url`), or `GET /wholesale/orders` for the list.
  </Step>
</Steps>

### Set your own prices (upcharge)

You are not limited to the wholesale price. Send `price_cents` on any order line to charge your customer your own price, anything **at or above** wholesale:

* The customer's card is charged your prices through Accept.js, never your card on file.
* VitaRelay keeps the wholesale amount; the difference is your earnings (`your_markup_cents` on the order). VitaRelay deducts a platform fee from your earnings (3% of what your customer pays, by default), and the rest is paid out through your normal payout schedule.
* A price below wholesale returns `422 price_below_cost`.
* Without a token on the order, it is created unpaid with `payment.status: pending` and the Accept.js keys in `checkout`; pay it with `POST /wholesale/orders/{id}/pay`.

There is no limit on how high your price goes. Leave `price_cents` off to buy at wholesale.

### Paying with Accept.js instead of your card on file

By default VitaRelay charges your default card on file. To charge a card entered on your page instead (yours or your customer's), use Accept.js:

1. `GET /wholesale/checkout-config` for the public keys and `accept_js_url`.
2. Load Accept.js and turn the card into a single-use token in the browser. Card data never touches your server.
3. Send the token with the order: add `opaque_data` and `billing` to `POST /wholesale/orders`. No saved card is needed.
4. If the card is declined, retry the same order with a new token using `POST /wholesale/orders/{id}/pay`.

The amount is always the wholesale total from our catalog; the price is never taken from the request.

Needs `orders:write` to order and `orders:read` / `products:read` to read. Test keys simulate everything and create nothing.

Prefer that VitaRelay takes your customer's card and pays out your markup? Use the store endpoints below instead.

## Let VitaRelay take your customer's card (store)

Run your own landing page and let VitaRelay do fulfillment, payment processing and shipping, and take your customer's card. This is for **research-use (R\&D) products sold to customers, not patients**: no patient, intake or prescription is created. You set the price your customers pay; the difference between your price and VitaRelay's price is yours (`your_markup_cents` on each order). 503A and 503B products need a patient and a prescriber, so they stay on the VitaClinic API.

<Steps>
  <Step title="See what you can sell">
    `GET /store/catalog` lists every R\&D product with VitaRelay's price (`base_price_cents`), your current price (`price_cents`) and the shipping methods. Your store is created the first time you call any store endpoint; it stays private unless you publish it in the app.
  </Step>

  <Step title="Set your prices">
    `PUT /store/products/{product_id}` with `price_cents` (the exact price, never below VitaRelay's), or `markup_percent` / `markup_flat_cents`, and `listed: true`. Show your customers `price_cents`.
  </Step>

  <Step title="Take the card on your page">
    `GET /store/checkout-config` returns the Accept.js keys. Load Accept.js from `accept_js_url` and turn the card into a single-use token in the customer's browser.
  </Step>

  <Step title="Create the order">
    `POST /store/orders` with `items`, `customer`, `shipping_address`, the buyer's `attestation` (21+, research use only, terms accepted) and your `external_order_id`. The order is unpaid and the response has the total.
  </Step>

  <Step title="Charge it">
    `POST /store/orders/{id}/pay` with the Accept.js token as `opaque_data` and the card's `billing` details. On success VitaRelay fulfills and ships. A declined card returns `402 card_declined`; the customer can retry on the same order.
  </Step>

  <Step title="Track it">
    `GET /store/orders/{id}` for status and payment, or `GET /store/orders` for the list.
  </Step>
</Steps>

Scopes: `store:read` (catalog and orders) and `store:write` (prices, orders, payment, checkout config). Store endpoints are for rep accounts and need the storefront enabled for your account; otherwise they return `403 not_available` or `403 store_not_available`. Test keys simulate everything and create nothing.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.