> ## 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.

# Customers API (CRM)

> Connect your CRM to your pharmacy's providers: send converted providers, get order and lifecycle webhooks, export your records.

The Customers API lets a CRM such as GoHighLevel work with the providers your pharmacy serves, without opening the app.

> [card] Send a converted provider
>
> `POST /customers` creates the provider (and its portal invite) and returns the VitaRelay id to store in your CRM.

> [card] Get events
>
> Webhooks for order shipped, delivered and cancelled, customer reorder due and lapsed, and partner dormant.

> [card] Export your providers
>
> `GET /customers` returns every provider with its last order and where it is in its order cycle.

## Authentication

Create a key under **Settings → API keys** with `customers:read` and/or `customers:write`. Send it as `Authorization: Bearer vr_live_...`. A key belongs to your pharmacy, and your pharmacy must have customers enabled.

* **Test keys** (`vr_test_...`) can read. Their writes are acknowledged with `"sandbox": true` and change nothing, so you can wire up your CRM safely first.
* Keys are rate limited per key; `429 rate_limited` includes a `Retry-After` header.

## 1. CRM to VitaRelay: a provider converted

```bash theme={null}
curl -X POST https://vitarelay.com/api/public/v1/customers \
  -H "Authorization: Bearer vr_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "ghl-contact-4471",
    "name": "Jason Meds",
    "contact": { "first_name": "Jason", "last_name": "Tests", "email": "owner@jasonmeds.com", "phone": "(229) 719-4222" },
    "address": { "line1": "12 Main St", "city": "Albany", "state": "GA", "zip": "31701" },
    "consent": { "sms": { "granted": true, "at": "2026-10-19T14:02:00Z", "source": "ghl-form" }, "email": { "granted": true } },
    "attribution": { "source": "facebook", "campaign": "oct-launch" },
    "booking_ref": "bk_90812"
  }'
```

| Field | Required | Notes |
| - | - | - |
| `external_id` | yes | Your CRM contact id. Repeating it updates the provider instead of creating another. |
| `name` | yes | The clinic or practice name. |
| `contact.email` | yes | Becomes the provider's login. It must not already have an account. |
| `address` | yes | `line1`, `city`, `state` (2 letters), `zip`. |
| `contact.first_name` / `last_name` / `phone`, `phone` | no | The clinic phone defaults to the contact's phone, which must be a valid 10-digit number if either is sent. |
| `prescribers` | no | NPI, state license and, for controlled substances, DEA. Without any, the provider is a tele clinic that uses the doctor network. |
| `consent`, `attribution`, `booking_ref` | no | Stored on the provider and returned on every read. `PATCH /customers/{id}` updates them later. |
| `send_invite` | no | `false` creates the provider without emailing the invite. |

The response (201 created, 200 updated) carries `data.id`, the VitaRelay id to store in your CRM, and the provider's signup state.

## 2. VitaRelay to CRM: webhooks

```bash theme={null}
curl -X POST https://vitarelay.com/api/public/v1/customers/webhooks \
  -H "Authorization: Bearer vr_live_..." -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/vitarelay"}'
```

The signing `secret` is returned once. Verify each request with the `X-Webhook-Signature` header (`sha256=` plus the hex HMAC-SHA256 of the exact body, keyed with the secret), and deduplicate on `X-Webhook-Delivery`.

| Event | When |
| - | - |
| `order.shipped` | A customer's order shipped (tracking number, carrier, link). |
| `order.delivered` | The carrier reports delivery. |
| `order.cancelled` | A customer's order was cancelled. |
| `customer.reorder_due` | A customer passed the reorder window since its last order. |
| `customer.lapsed` | A customer passed the lapsed window. |
| `partner.dormant` | A partner rep's recruited clinics placed no order for the dormant window. |

Order events carry the order and a `customer` object (`id`, `external_id`, `name`, `email`), so you can find the contact in your CRM. Lifecycle events carry the customer (or `partner`), the last order and the days since it. Each lifecycle event fires once per last order; a new order restarts the clock.

The windows default to 30, 60 and 90 days since the last order. Change them with `PUT /customers/settings` (`reorder_due_days`, `lapsed_days`, `dormant_days`). Customers already past a window when you connect do not fire events; read their `lifecycle_status` from `GET /customers`.

### Test it

Send yourself an event with `POST /customers/webhooks/ping` and `{"id": "<endpoint id>"}`: a `ping` arrives at your URL, signed like the real ones.

## 3. Export your providers

`GET /customers` lists them (filter with `external_id` or `email`; page with `limit` and `cursor`). `GET /customers/{id}` returns one.

## Errors

| Status | Code | Meaning |
| - | - | - |
| 409 | `duplicate_request` | The contact email already has an account or a pending invitation |
| 422 | `validation_failed` | A field is missing or invalid; the message names it |
| 404 | `not_found` | No such provider or webhook for your pharmacy |
| 403 | `insufficient_scope` / `not_available` | The key cannot do this, or customers are not enabled for your pharmacy |


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