Cross-pharmacy routing & failover
One approved order, routed to the right pharmacy per your policy — with single-hop automatic failover when a pharmacy rejects.
You send one approved order; neolife decides which pharmacy fills each line and, if a pharmacy rejects, moves it to an alternate — automatically, exactly once. You don't hard-code a pharmacy into your integration. You set a policy, and the rail resolves it per order.
Why routing is a rail feature#
A single cart can span pharmacies: one product is compounded at pharmacy A, another at pharmacy B. And a pharmacy that accepts an order today can reject it tomorrow — out of stock, out of state, capacity. Baking a pharmacy choice into your app means re-shipping code every time that changes.
Instead, each of your pharmacy connections is a routable destination, and a per-tenant routing policy decides which connection each line goes to. Change pharmacies, add a backup, or flip on failover without touching your integration.
How an order routes#
When an order is placed, neolife splits the cart into one child order per pharmacy it routes to. The patient sees a single parent order; each pharmacy receives only its own lines as its own shipment. Splitting and the final status flip commit together — a mid-split failure rolls the whole thing back, so you never get a half-placed order.
Routing resolves each line under your policy's mode:
| Mode | How a line resolves |
|---|---|
per_product |
The product's own default pharmacy, unless an explicit per-product override points elsewhere. This is the default. |
per_category |
A category → pharmacy map (e.g. all peptides to one pharmacy), falling back to the product default. |
rule_based |
Prefers a computed alternate (e.g. an in-stock or preferred equivalent), falling back to the product default. |
manual |
The provider's explicit pick at approval, falling back to the product default. |
A line that resolves to no pharmacy is reported as unroutable rather than silently dropped — you see its productId in the response and can fix the mapping.
Preview a route before you commit#
POST /v1/orders/routing/plan shows exactly where a cart would go under your current policy — no order is created. Use it to render a shipment breakdown at checkout, or to validate a cart in sandbox.
curl -X POST https://api.neolife.health/v1/orders/routing/plan \
-H "Authorization: Bearer $NEOLIFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "productId": "prod_glp1_a" },
{ "productId": "prod_peptide_b", "manualConnectionId": "conn_alt_pharmacy" }
]
}'
{
"policy": { "mode": "per_product", "failover": "auto" },
"groups": [
{
"connectionId": "conn_primary",
"pharmacyName": "Primary Compounding",
"productIds": ["prod_glp1_a"]
},
{
"connectionId": "conn_alt_pharmacy",
"pharmacyName": "Backup Compounding",
"productIds": ["prod_peptide_b"]
}
],
"unroutableProductIds": []
}
Each entry in groups becomes one child order when you actually place the cart. unroutableProductIds lists anything that couldn't be resolved.
Failover: single-hop, retry once, never twice#
If a pharmacy rejects a submitted child order, failover moves that child to an alternate pharmacy so it can resubmit — instead of stranding the patient's order. This is governed by your policy's failover setting:
| Failover | Behavior |
|---|---|
auto |
On rejection, the rail picks the next alternate pharmacy that carries an equivalent for every line in the order and hasn't been tried yet. |
manual |
Rejection surfaces to an operator, who names the alternate pharmacy explicitly. |
single_source |
No failover — a rejected order stays rejected for you to handle. |
Two guarantees make this safe to rely on:
- The alternate must cover the whole child order. For a multi-line child, the target pharmacy must carry an equivalent for every line — the rail takes the intersection of each line's candidate pharmacies, so failover never partially fills.
- No pharmacy is tried twice. Every attempted connection is remembered on the order. A reroute always picks a new destination, so there's no ping-pong between two pharmacies and no duplicate dispatch.
Combined with the single-dispatch guarantee on submission and idempotency, a rejected order fails over to a fresh pharmacy and resubmits without any risk of two physical shipments.
Manual reroute#
You can also reroute a specific child order yourself — for example, from an operator tool or an agent reacting to a rejection webhook:
curl -X POST https://api.neolife.health/v1/orders/ord_child_123/reroute \
-H "Authorization: Bearer $NEOLIFE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c9e6a41-2b83-4f5d-9a10-6e2f8c1d4b70" \
-d '{ "connectionId": "conn_alt_pharmacy" }'
{
"rerouted": true,
"connectionId": "conn_alt_pharmacy"
}
The chosen pharmacy still has to be a valid alternate that carries every line and hasn't been tried — a request for an ineligible or already-tried pharmacy returns rerouted: false with a null connection. On success the order returns to an approved state, ready to resubmit. A reroute is a mutation, so send an Idempotency-Key.
Related#
- Orders — the lifecycle a routed child order moves through.
- Pharmacies — the connections routing resolves against.
- Idempotency — why reroute and submit are safe to retry.