Pharmacy Router API
Centralized pharmacy submission API for routing prescriptions to the correct 503B compounding pharmacy.
Overview
The Pharmacy Router is a centralized API that accepts prescription submissions, determines the correct compounding pharmacy based on the patient's state, translates the order into the pharmacy's native format, and tracks the order through fulfillment.
Instead of each service integrating directly with multiple pharmacies, callers submit a single standardized payload and let the router handle pharmacy selection, format translation, submission, and status tracking.
Base URL
https://api.yourera.com/rx
How It Works
- Caller submits a
PrescriptionSubmissiontoPOST /rx/prescriptions/submit - Router resolves the target pharmacy based on patient state and routing config
- Router translates the payload into the pharmacy's native format and submits
- Synchronous response includes
submissionId,pharmacy, andstatus - Router sends an HMAC-signed callback to the caller's
callbackUrl - When pharmacy webhooks arrive (shipping updates, status changes), router updates the submission and sends another callback
Authentication
All /rx/prescriptions/* endpoints require HMAC-SHA256 authentication. Each API client
is issued an API key (public identifier) and an API secret
(used for signing).
Required Headers
| Header | Description |
|---|---|
X-API-Key |
Your public API key identifier |
X-Timestamp |
Current timestamp in ISO 8601 format. Must be within 5 minutes of server time (replay protection). |
X-Signature |
HMAC-SHA256 hex digest of the signing string |
Signature Formula
The signing string is the timestamp, a dot, and the JSON-stringified request body:
HMAC-SHA256(apiSecret, timestamp + '.' + JSON.stringify(body))
The result is encoded as a lowercase hex string.
X-Timestamp header must be within 5 minutes of the server's current time.
Requests with expired timestamps are rejected with 401.
Node.js Example
import crypto from 'node:crypto';
const API_KEY = 'your-api-key';
const API_SECRET = 'your-api-secret';
function signRequest(body) {
const timestamp = new Date().toISOString();
const bodyStr = JSON.stringify(body);
const signature = crypto
.createHmac('sha256', API_SECRET)
.update(timestamp + '.' + bodyStr)
.digest('hex');
return { timestamp, signature };
}
// Usage
const body = { /* PrescriptionSubmission */ };
const { timestamp, signature } = signRequest(body);
const response = await fetch('https://api.yourera.com/rx/prescriptions/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body: JSON.stringify(body),
});
body used for signing must be the exact same string sent as the request body.
Call JSON.stringify() once, use it for both signing and the fetch body.
Submit Prescription
Submit a prescription for pharmacy routing. The router resolves the target pharmacy,
translates the payload, submits the order, and returns the result synchronously.
An HMAC-signed callback is also sent asynchronously to the provided callbackUrl.
Request Headers
| Header | Value |
|---|---|
Content-Type | application/json |
X-API-Key | Your API key |
X-Timestamp | ISO 8601 timestamp |
X-Signature | HMAC-SHA256 hex signature |
Request Body: PrescriptionSubmission
Top-level Fields
| Field | Type | Description | |
|---|---|---|---|
source |
string |
required | Identifier for the calling system (e.g. "guide-glp", "yourera-orchestrator") |
sourceOrderId |
string |
required | The caller's unique order/reference ID. Used for deduplication (unique per source). |
callbackUrl |
string |
required | URL to receive HMAC-signed status callbacks. Must be a valid URL. |
patient |
object |
required | Patient demographics. See patient object. |
shipTo |
object |
required | Shipping address. See shipTo object. |
prescriber |
object |
required | Prescribing clinician details. See prescriber object. |
medication |
object |
required | Prescription details. See medication object. |
routing |
object |
optional | Routing overrides. See routing object. |
test |
boolean |
optional | When true, submit to pharmacy sandbox/test environments. No real prescriptions are created. See Test Mode. |
clinical |
object |
optional | Clinical data (allergies, conditions, medications). See clinical object. |
patient Object
| Field | Type | Description | |
|---|---|---|---|
firstName |
string |
required | Patient's first name |
lastName |
string |
required | Patient's last name |
dob |
string |
required | Date of birth (e.g. "1990-01-15") |
gender |
"male" | "female" |
required | Patient gender |
phone |
string |
required | Patient phone number |
email |
string |
optional | Patient email address |
shipTo Object
| Field | Type | Description | |
|---|---|---|---|
firstName |
string |
required | Recipient first name |
lastName |
string |
required | Recipient last name |
phone |
string |
required | Recipient phone number |
addressLine1 |
string |
required | Street address line 1 |
addressLine2 |
string |
optional | Street address line 2 (apt, suite, etc.) |
city |
string |
required | City |
state |
string |
required | Two-letter state code (e.g. "TX") |
zip |
string |
required | ZIP code (min 5 characters) |
prescriber Object
| Field | Type | Description | |
|---|---|---|---|
firstName |
string |
required | Prescriber first name |
lastName |
string |
required | Prescriber last name |
npi |
string |
required | NPI number (10 digits) |
deaNumber |
string |
optional | DEA registration number |
licenseNumber |
string |
optional | State license number |
licenseState |
string |
optional | State of licensure |
phone |
string |
optional | Prescriber phone |
fax |
string |
optional | Prescriber fax number |
email |
string |
optional | Prescriber email |
address |
object |
optional | Prescriber address: { line1, city, state, zip } |
signatureBase64 |
string |
optional | Base64-encoded prescriber signature image. Used by Strive as signed_prescription_image. |
medication Object
| Field | Type | Description | |
|---|---|---|---|
name |
string |
required | Medication name (e.g. "Semaglutide 2.5mg/mL"). Used for SKU/catalog mapping. |
sig |
string |
required | Prescriber instructions (sig). E.g. "Inject 0.25mL subcutaneously once weekly" |
quantity |
number |
required | Quantity to dispense (must be positive) |
daysSupply |
number |
required | Days supply (must be positive) |
refills |
number |
required | Number of refills authorized (0 or more) |
clinicalJustification |
string |
optional | Clinical justification for the prescription |
note |
string |
optional | Additional notes for the pharmacy |
routing Object
| Field | Type | Description | |
|---|---|---|---|
patientState |
string |
required | Two-letter state code for routing. If omitted, the router falls back to shipTo.state. |
preferredPharmacy |
string |
optional | Force a specific pharmacy (e.g. "boothwyn", "strive"). Bypasses state-based routing. |
clinical Object
| Field | Type | Description | |
|---|---|---|---|
allergies |
object |
optional | { known: boolean, entries?: string[] } - Patient allergies |
conditions |
object |
optional | { known: boolean, entries?: string[] } - Medical conditions |
medications |
object |
optional | { known: boolean, entries?: string[] } - Current medications |
billTo |
"patient" | "practice" |
optional | Who to bill. Defaults to "practice" for Strive orders. |
Example Request
{
"source": "guide-glp",
"sourceOrderId": "ord_abc123",
"callbackUrl": "https://api.example.com/webhooks/pharmacy-router",
"patient": {
"firstName": "Jane",
"lastName": "Smith",
"dob": "1990-03-15",
"gender": "female",
"phone": "(555) 123-4567",
"email": "jane.smith@example.com"
},
"shipTo": {
"firstName": "Jane",
"lastName": "Smith",
"phone": "(555) 123-4567",
"addressLine1": "123 Main St",
"addressLine2": "Apt 4B",
"city": "Austin",
"state": "TX",
"zip": "78701"
},
"prescriber": {
"firstName": "Ali",
"lastName": "Nolan",
"npi": "1982609764",
"phone": "(555) 987-6543",
"email": "dr.nolan@example.com",
"address": {
"line1": "456 Medical Pkwy",
"city": "Dallas",
"state": "TX",
"zip": "75201"
}
},
"medication": {
"name": "Semaglutide 2.5mg/mL",
"sig": "Inject 0.25mL subcutaneously once weekly for 4 weeks",
"quantity": 1,
"daysSupply": 28,
"refills": 0,
"clinicalJustification": "BMI 32, type 2 diabetes"
},
"routing": {
"patientState": "TX"
},
"test": false
}
Response
Success (201)
{
"submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"pharmacy": "strive",
"status": "submitted",
"pharmacyOrderId": "STV-ORD-12345"
}
Validation Error (400)
{
"error": "Validation failed",
"details": {
"fieldErrors": {
"patient.dob": ["Required"]
},
"formErrors": []
}
}
Authentication Error (401)
{
"error": "Invalid signature"
}
No Route (422)
{
"error": "No pharmacy route configured for state: MN"
}
Pharmacy Error (502)
{
"submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"pharmacy": "boothwyn",
"status": "failed",
"pharmacyOrderId": null,
"error": "Boothwyn API error 500: Internal Server Error"
}
Get Submission
Retrieve the full submission record by ID. Returns the current status, tracking information, and full request/response payloads. Only the API client that created the submission can access it.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
uuid |
The submissionId returned from the submit endpoint |
Request Headers
Requires the same HMAC authentication headers as the submit endpoint. For GET requests, the body is empty, so the signing string is:
HMAC-SHA256(apiSecret, timestamp + '.' + '{}')
{} since there is no request body. The request body sent to the server should also be empty or {}.
Response (200)
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"apiKeyId": "key-uuid",
"source": "guide-glp",
"sourceOrderId": "ord_abc123",
"callbackUrl": "https://api.example.com/webhooks/pharmacy-router",
"patientState": "TX",
"medicationName": "Semaglutide 2.5mg/mL",
"pharmacy": "strive",
"pharmacyOrderId": "STV-ORD-12345",
"status": "shipped",
"trackingNumber": "1Z999AA10123456784",
"carrier": "UPS",
"errorMessage": null,
"requestPayload": { /* original PrescriptionSubmission */ },
"responsePayload": { /* pharmacy raw response */ },
"submittedAt": "2026-03-19T14:30:00.000Z",
"createdAt": "2026-03-19T14:30:00.000Z",
"updatedAt": "2026-03-20T09:15:00.000Z"
}
Error Responses
| Status | Description |
|---|---|
| 401 | Missing or invalid HMAC authentication |
| 403 | Submission belongs to a different API client |
| 404 | Submission not found |
Webhook: Boothwyn
Receives shipping and status updates from Boothwyn pharmacy. When an update is received, the router updates the submission record and sends an HMAC-signed callback to the original caller.
Authentication
| Header | Description |
|---|---|
x-webhook-secret |
Shared secret configured with Boothwyn |
Request Body
| Field | Type | Description | |
|---|---|---|---|
caseId |
string |
required | The Boothwyn case ID (matches pharmacyOrderId on the submission) |
trackingNumber |
string |
optional | FedEx tracking number |
rxStatus |
string |
optional | Status update: shipped, delivered, cancelled, processing |
Status Mapping
| Boothwyn Status | Router Status |
|---|---|
shipped | shipped |
delivered | delivered |
cancelled | cancelled |
processing | processing |
Response
{ "ok": true }
Webhook: Strive
Receives shipping and status updates from Strive (RxVortex) pharmacy. When an update is received, the router updates the submission record and sends an HMAC-signed callback to the original caller.
Authentication
| Header | Description |
|---|---|
x-webhook-secret |
Shared secret configured with Strive |
Request Body
| Field | Type | Description | |
|---|---|---|---|
tracking_id |
string |
required | Strive order tracking ID (matches pharmacyOrderId on the submission) |
trackingnumber |
string |
optional | Shipping tracking number |
rxstatus |
string |
optional | Status update: shipped, delivered, cancelled, processing, in-transit |
shippingcarrier |
string |
optional | Shipping carrier name |
Status Mapping
| Strive Status | Router Status |
|---|---|
shipped | shipped |
delivered | delivered |
cancelled | cancelled |
processing | processing |
in-transit | shipped |
Response
{ "ok": true }
Health Check
Returns service health status. No authentication required.
Response (200)
{
"status": "ok",
"service": "pharmacy-router",
"timestamp": "2026-03-19T14:30:00.000Z"
}
Pharmacy Routing
The router determines which pharmacy receives an order based on the patient's state.
Routing rules are stored in the router_pharmacy_config database table, with
each state mapped to one or more pharmacies with priority rankings.
Resolution Logic
- If
routing.preferredPharmacyis set, use it directly (bypass state routing) - Determine state from
routing.patientState, falling back toshipTo.state - Query active routing configs for that state, ordered by priority (highest wins)
- If no route exists, return
422with an error
Current Pharmacy Assignments
| Pharmacy | States | Notes |
|---|---|---|
| GMP (via Boothwyn) | AL, AZ, CO, CT, FL, GA, IL, IN, KS, KY, LA, NJ, NV, NY, OH, PA, TN, WI | 18 states. Uses the Boothwyn B2B adapter internally. |
| Strive | TX | Texas-only. Uses the RxVortex/Strive API. |
| Boothwyn | All remaining states (except excluded) | Fallback for states not covered by GMP or Strive. |
Excluded States
422 error: MN, CA, AR, NC, OK.
Priority System
Each state-pharmacy mapping has a priority integer. When multiple pharmacies
are configured for a state, the one with the highest priority (and isActive = true)
wins. This allows easy failover by adjusting priorities without removing configurations.
Adapters
Each pharmacy has an adapter that translates the standardized PrescriptionSubmission
into the pharmacy's native API format. Adapters handle authentication, format mapping,
and error normalization.
Boothwyn Adapter
Submits orders to the Boothwyn Nucleus B2B REST API. Used for both direct Boothwyn orders and GMP-routed orders (GMP fulfillment goes through the same Boothwyn B2B endpoint).
API Details
- Endpoint:
https://boothwynnucleus.azurewebsites.net/api/Prescriptions/B2B - Auth:
x-functions-keyheader with Azure function key - Test mode: Appends
?test=trueto URL - Order type:
1(standard) - Shipping method:
43 - Case ID: Generated UUID per submission
SKU Mapping
| Medication | SKU |
|---|---|
| Semaglutide | 12565 |
| Tirzepatide | 12850 |
| NAD+ | 12832 |
The adapter matches medication names by checking if the medication.name field
contains "semaglutide", "tirzepatide", or "nad" (case-insensitive). If no match is found,
the SKU is set to UNKNOWN_SKU and the pharmacy will reject the order.
Gender Mapping
"male" maps to "m", "female" maps to "f".
Strive Adapter
Submits orders to the Strive / RxVortex API. Uses OAuth 2.0 client credentials for authentication.
API Details
- Production URL: Configured via
STRIVE_API_URLenv var - Sandbox URL:
https://sandbox.rxvortex.net - Auth: OAuth 2.0 client credentials (
POST /api/v1/generate-access-token) - Token caching: Tokens cached for 23 hours (expire at 24h)
- Order endpoint:
POST /api/v1/orders
Catalog IDs
| Medication | Catalog ID |
|---|---|
| Semaglutide | Configured via STRIVE_CATALOG_SEMAGLUTIDE env var |
| Tirzepatide | Configured via STRIVE_CATALOG_TIRZEPATIDE env var |
Clinical Data Mapping
The clinical object is mapped to Strive's format:
allergies maps directly,
conditions maps to diseases,
medications maps directly.
Each entry in entries[] is wrapped as { description: "..." }.
PioneerRx Adapter
Callbacks
The router sends HMAC-signed HTTP POST callbacks to the callbackUrl you provide
in the submission. Callbacks are fired at two points:
- After initial submission -- with the pharmacy result (success or failure)
- On webhook updates -- when a pharmacy sends a shipping/status update
Callback Authentication
Callbacks include X-Timestamp and X-Signature headers. The signature
is computed using the same HMAC-SHA256 formula as request authentication, using your
apiSecret:
HMAC-SHA256(apiSecret, timestamp + '.' + body)
You should verify this signature on your end to confirm the callback is authentic.
Callback Payload (Initial Submission)
{
"submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sourceOrderId": "ord_abc123",
"pharmacy": "boothwyn",
"status": "submitted",
"pharmacyOrderId": "case-uuid-here",
"error": null
}
Callback Payload (Webhook Update)
{
"submissionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"sourceOrderId": "ord_abc123",
"pharmacy": "boothwyn",
"status": "shipped",
"pharmacyOrderId": "case-uuid-here",
"trackingNumber": "794644790132",
"carrier": "FedEx"
}
Verifying Callbacks (Node.js)
import crypto from 'node:crypto';
function verifyCallback(req, apiSecret) {
const timestamp = req.headers['x-timestamp'];
const signature = req.headers['x-signature'];
const bodyStr = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', apiSecret)
.update(timestamp + '.' + bodyStr)
.digest('hex');
const sigBuf = Buffer.from(signature, 'hex');
const expBuf = Buffer.from(expected, 'hex');
return sigBuf.length === expBuf.length
&& crypto.timingSafeEqual(sigBuf, expBuf);
}
GET /rx/prescriptions/:id endpoint to poll for status as a fallback.
Test Mode
Set "test": true in the request body to submit orders to pharmacy sandbox
and test environments. No real prescriptions are created in test mode.
Per-Pharmacy Behavior
| Pharmacy | Test Mode Behavior |
|---|---|
| Boothwyn |
Appends ?test=true to the B2B API URL. Boothwyn processes the order
in their test database -- it will not be fulfilled or shipped.
|
| Strive |
Routes to https://sandbox.rxvortex.net instead of the production URL.
Orders are processed in the Strive sandbox environment.
|
| PioneerRx |
Planned: Will use the executeMethodTest endpoint (separate
test database) instead of the production executeMethod endpoint.
|
"test": true. The router records
test submissions the same way as production ones, so you can verify the full
callback and status flow without creating real pharmacy orders.
Example: Test Submission
const body = {
source: 'guide-glp',
sourceOrderId: 'test-001',
callbackUrl: 'https://api.example.com/webhooks/pharmacy-router',
test: true,
// ... patient, shipTo, prescriber, medication ...
};
const { timestamp, signature } = signRequest(body);
const res = await fetch('https://api.yourera.com/rx/prescriptions/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body: JSON.stringify(body),
});
const data = await res.json();
console.log(data);
// { submissionId: "...", pharmacy: "strive", status: "submitted", ... }