Developers
An API designed around events, not polling.
Authentication
Coming in v1.1 The API uses OAuth 2.0 client credentials. You exchange a client id and secret for a short-lived bearer token, and every request is scoped to your partner account. Secrets are issued per environment, can be rotated without downtime, and are never shown again after creation.
curl https://api.vayraplatform.com/oauth/token \
-d grant_type=client_credentials \
-d client_id=$VAYRA_CLIENT_ID \
-d client_secret=$VAYRA_CLIENT_SECRETconst body = new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.VAYRA_CLIENT_ID!,
client_secret: process.env.VAYRA_CLIENT_SECRET!,
});
const res = await fetch("https://api.vayraplatform.com/oauth/token", { method: "POST", body });
const { access_token, expires_in } = await res.json();Resources
Coming in v1.1 Resources carry identifiers and status. Clinical content stays inside the clinical record and is not exposed through the API.
| Resource | Path | Covers |
|---|---|---|
| Partners | /v1/partner | Your account, programs and configuration |
| Patients | /v1/patients | Identifiers and status. Clinical content stays in the record |
| Providers | /v1/providers | Clinicians and coverage by state |
| Encounters | /v1/encounters | Async reviews and visits, with decision status |
| Orders | /v1/orders | Pharmacy orders and shipment tracking |
| Prescriptions | /v1/prescriptions | Prescription status and routing |
| Payments | /v1/payments | Charges, refunds, disputes and subscriptions |
| Webhooks | /v1/webhook-endpoints | Register endpoints and rotate signing secrets |
curl https://api.vayraplatform.com/v1/orders?limit=25 \
-H "Authorization: Bearer $VAYRA_ACCESS_TOKEN"const res = await fetch("https://api.vayraplatform.com/v1/orders?limit=25", {
headers: { Authorization: `Bearer ${process.env.VAYRA_ACCESS_TOKEN}` },
});
if (!res.ok) throw new Error(`Vayra API ${res.status}`);
const { data, nextCursor } = await res.json();Pagination and errors
Coming in v1.1 Lists are cursor-paginated. Pass limit and the nextCursor from the previous page. Cursors are stable while you page, even as new records arrive.
Errors use standard HTTP status codes with a consistent body: a type, a machine-readable code, a human message, the parameter at fault where there is one, and a request id to quote to support.
{
"error": {
"type": "invalid_request",
"code": "parameter_invalid",
"message": "limit must be between 1 and 100",
"param": "limit",
"requestId": "req_8c1f02"
}
}Webhooks
Coming in v1.1 Register an HTTPS endpoint and choose the events you want. Every delivery is signed with your endpoint secret, retried with backoff when your endpoint fails, and safe to process more than once using the event id.
| Event | Sent when |
|---|---|
| intake.completed | A patient finished an intake and it entered review |
| encounter.decided | A clinician approved, declined or converted a case |
| prescription.routed | A prescription was sent to a pharmacy |
| order.shipped | The pharmacy shipped an order, with tracking |
| order.exception | An order needs attention: address, loss or damage |
| subscription.renewed | A recurring payment succeeded |
| subscription.payment_failed | A recurring payment failed and a retry is scheduled |
| dispute.opened | A chargeback arrived, with evidence gathered |
| assistant.escalated | Vayra Intelligence handed a patient to the care team |
Verify the signature
import { createHmac, timingSafeEqual } from "node:crypto";
// Header format: "t=<unix seconds>,v1=<hex signature>"
export function verifyVayraSignature(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=") as [string, string]));
const timestamp = Number(parts.t);
if (!parts.v1 || !Number.isFinite(timestamp)) return false;
// Reject replays older than five minutes.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
const received = Buffer.from(parts.v1, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Rate limits
Coming in v1.1 Limits are per partner and per endpoint, published with your sandbox credentials. Responses carry the remaining allowance in headers, and a 429 includes a retry hint.
Sandbox
Coming in v1.1 A separate environment with test patients, test pharmacies and test payments, so you can run the whole lifecycle without touching real data. Request access through the contact page.
Changelog
API changes will be versioned and announced here, with deprecation notice before any breaking change. No entries yet: the first entry will be the v1.1 release.
Plan your integration with our engineers
Tell us what you need to connect. We will show you the event model and the exports available today.