Appearance
Catalog and availability
Summary: GET /catalog returns the products your account's default clinic is set up to order, with the price, the states each can ship to, and flags for any that are held or backordered right now. Read it instead of keeping your own list, subscribe to product.availability_changed to hear quickly when a product runs out or comes back, and still re-read the catalog regularly: it is the record, the webhook is a nudge. For a specific clinic, POST /orders/check gives the exact answer.
Reading the catalog
GET /api/inbound/{slug}/catalog returns { "ok": true, "catalog": [...], "retired": [...] }, sorted by SKU.
| Field | Meaning |
|---|---|
sku | The code to put in items[].sku. |
aliases | Other codes that also resolve to this product for you. |
name, strength, formFactor | How to show it. formFactor is free text such as Vial, Pen, Cartridge or Nasal Spray; do not rely on a fixed list or letter case. |
imageUrl | A generic product image you may show, or null. |
clinicPriceCents | Your price per unit, in cents. |
pricingSource | Which price rule produced it. Informational. |
availableStates | The two-letter states an order for this SKU can ship to today. |
backordered, expectedBackAt | Out of stock, and when we expect it back if known. |
unavailableUntil | A future date before which it cannot be ordered, or null. |
Only products that have a price for you are listed. The list and the prices are worked out for your account's default clinic, and the ship-to states at account level. Another clinic can have its own pricing, its own catalog and its own routing rules, so POST /orders/check with that clinic's clinic.external_id is the exact answer for it.
Ship-to states
A product can only be shipped to a state where a pharmacy that fills it is licensed, and coverage differs by product. availableStates applies the licensing and state rules order create applies, at account level, so it is the right list to offer. It does not include routing rules set for one particular clinic, which can narrow or widen what that clinic can order, so POST /orders/check is the final word for a given clinic and state (a refusal is 422 ship_state_not_licensed, product_unavailable_in_state or no_pharmacy_can_fill). Coverage changes as licensing changes, so read it rather than hard-coding it.
Availability changes
A product can stop being orderable for three independent reasons: it is switched off, it is held until a launch date, or it is out of stock. Subscribe to product.availability_changed to hear about these within minutes, and keep re-reading the catalog on a schedule: a delivery can be missed, and the catalog is the record.
json
{
"event": "product.availability_changed",
"partner": "acme-health",
"environment": "production",
"product": { "sku": "EXAMPLE-SKU-1", "revision": 7, "orderable": false, "reasons": ["backordered"], "unavailableUntil": null, "expectedBackAt": "2026-11-03T05:00:00.000Z" },
"previous": { "orderable": true, "reasons": [], "unavailableUntil": null, "expectedBackAt": null },
"observedAt": "2026-10-25T18:00:00.000Z"
}product.orderableis the answer to act on.reasonscan holdinactive,heldandbackorderedat once, so a product coming back in stock is not necessarily orderable.revisionincreases with each change for a SKU. Keep the highest you have seen and ignore a lower one that arrives late.- You only receive events for SKUs in your catalog.
An order for a product that is not orderable is refused: 422 product_backordered (with expectedBackAt), 422 product_not_yet_available (with availableAt), or, for a product that has been switched off, 422 unknown_product with reason: product_inactive.
Retired products
When we retire a product for good, it leaves catalog and appears in retired as { "sku": "...", "retiredAt": "..." } for 90 days, so you can remove it from your own lists. Nothing in retired can be ordered (422 product_retired), and no replacement is chosen for you.

