Appearance
Sandbox testing
Summary: a sandbox key talks to the same API at the same URL and gets the same validation and the same error codes as production, but no order ever reaches a pharmacy, nothing ships and nothing is billed. Orders walk a simulated lifecycle that sends real, signed webhooks to your sandbox endpoints.
What is the same as production
- Signing, rate limits and validation work exactly as in production.
- Order checks and order create apply the same rules: missing prescriber details, the patient's sex, unknown or retired SKUs, ship-to states, duplicate order ids and idempotency all fail the same way, with the same codes. A body that passes in sandbox passes the same checks in production.
- Webhooks are signed and retried the same way.
What is different
| Sandbox | |
|---|---|
| Pharmacy | None. pharmacyId stays null and no pharmacy is ever contacted. |
| Lifecycle | Simulated: received, then routed at once, shipped about 30 seconds later, delivered about 60 seconds after that. |
| Tracking | trackingNumber is SANDBOX-<axiomOrderId> and carrier is SANDBOX. |
| Billing | Nothing is billed. GET /invoices and GET /billing/orders return empty lists. |
| Credit limits | Not applied. POST /orders/check lists credit_cap and exposure_cap in notChecked. |
| Clinic agreements | Enforced in sandbox by default only for partner-attested accounts. For e-signature accounts, sandbox orders for an unsigned clinic pass unless we switch enforcement on for your sandbox; ask us when you test that path. When the check is skipped, notChecked includes agreement. |
| Data | Sandbox orders, tickets, notices and idempotency keys are separate from production ones and invisible to production keys, and the reverse. A sandbox key never changes a real clinic. Enrolling a clinic with a sandbox key checks the request exactly as production does and records only the clinic's external_id, so later sandbox orders for it pass a partner-attested agreement check. Clinic updates, bank details and identity changes from a sandbox key are checked and answered with "saved": false, and a sandbox order never creates a clinic. A sandbox key sees and manages only sandbox webhook endpoints. Sandbox orders send no email to anyone at a clinic. |
Because a sandbox order is routed almost immediately, the window to edit it (which needs received) is a fraction of a second, and the window to cancel it (which needs routed or earlier) is about 30 seconds.
A test plan
Work through these before you ask to go live. Each one is a request you should be able to send, and a response your code should handle.
| Scenario | How to cause it | Expect |
|---|---|---|
| Happy path | A complete order to a state in the SKU's availableStates | 201, then order.status_changed for routed, shipped, delivered |
| Check refuses, no payment taken | Leave out physician.phone and physician.address | Check: wouldAccept: false with prescriber_incomplete. Your checkout must stop before payment. |
| Missing order details at create | The same body sent to POST /orders | 422 prescriber_incomplete with blockers |
| Unknown product | "sku": "NOT-A-REAL-SKU" | 422 unknown_product, reason: unrecognised |
| State not covered | A state missing from the SKU's availableStates (and from every pharmacy's licences) | 422 ship_state_not_licensed or no_pharmacy_can_fill |
| Retry after a timeout | Send the same create twice with the same key and body | The same 201 both times, one order |
| Key reused for a different order | Same key, different body | 409 idempotency_key_reused |
| Header and body keys differ | Change only the body's idempotency_key | 400 idempotency_key_mismatch |
| Duplicate order id | A new key, same partner_order_id | 409 partner_order_id_duplicate with the existing axiomOrderId |
| Cancel | Cancel within about 30 seconds of create | 200, status: cancelled, and an order.status_changed |
| Cancel too late | Cancel after it shows shipped | 409 order_not_cancellable |
| Bad signature | Change one character of the body after signing | 401 hmac_invalid |
| Clock skew | Sign with a timestamp 10 minutes old | 401 hmac_timestamp_skew |
| Webhook verification | Change the stored endpoint secret | Your receiver rejects the next delivery |
| Webhook outage | Make your endpoint answer 503 for a few minutes | Retries arrive; the portal shows the attempts; replay works |
Tools in the partner portal
- Requests shows the requests your keys made that passed authentication, with their status and error code. A request refused during signing or the account checks (for example
401 hmac_invalidor403 production_not_enabled) is not listed. - Deliveries shows each webhook delivery with its attempt count and its latest response code, and lets you replay one.
- Go-live shows which go-live items are done.

