Concepts

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.