Appearance
Errors and rate limits
Summary: every error is JSON with a machine-readable error code, sometimes with details beside it. Branch on the HTTP status and error, never on message, which is for people and can change. Retry 429, 503 and 5xx with the same Idempotency-Key; fix and resend anything else.
The error format
json
{ "error": "ship_state_not_licensed", "state": "AK" }erroris always present and stable.- Details depend on the code:
sku,state,reason,blockers,issues,retryableand so on. They are listed with each code below and in the API reference. - Validation errors carry
issues(each with apathand amessage). The clinic endpoints also returnfields, the list of dotted field paths. A message can quote what you sent (for example the value given for an enum field), so do not log error bodies anywhere you would not log the request. - Every response, success or error, has an
X-Request-Idheader. Log it and quote it when you contact us.
Retrying
| Status | Retry? |
|---|---|
400, 401, 403, 404, 409 (except the in-flight codes), 413, 415, 422, 423 | No. Fix the request or the account state first. |
409 order_in_flight, 409 idempotency_key_in_flight | Yes, shortly, with the same key. If order_in_flight persists, the key belongs to an earlier failed or cancelled order: read that order first (GET /orders/{partnerOrderId}) and follow the failed-order guidance; never resubmit one whose failure.code is pharmacy_outcome_unknown. Only then resubmit, under a new key. |
429 rate_limit_exceeded | Yes, after Retry-After seconds. |
429 bank_change_quota | Not soon: one bank account change per clinic per 24 hours. There is no Retry-After. |
503 | Yes, after Retry-After seconds when present, otherwise a few seconds with backoff. |
500 | Yes, with backoff and the same Idempotency-Key, so a request that did go through is not repeated. |
| Timeout or network error | Yes, with the same Idempotency-Key. |
Rate limits
Limits are per account, counted over a 60-second window that starts with the first request in it. For the per-minute limits below, requests are counted before the signature is checked, so failed attempts count too. The upload limit counts only signed uploads. Attachment downloads are not rate limited.
| Traffic | Limit |
|---|---|
Writes (POST, PUT, PATCH under /api/inbound/{slug}) | 120 per minute |
Reads (GET under /api/inbound/{slug}, except attachment downloads) | 600 per minute |
POST /orders/check | 600 per minute, counted separately, so checks never use up your order budget |
POST /uploads | 100 per hour |
Webhook endpoint API (/api/partner/{slug}/webhooks) | 60 writes and 300 reads per minute |
Over a limit you get 429 with a Retry-After header and a body of { "error": "rate_limit_exceeded", "retryAfterSec": 12 }.
General
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 400 | invalid_payload | The body could not be read (for example an unsupported encoding). |
| 400 | validation_failed | The body failed validation. See issues. |
| 400 | invalid_query | A query parameter is invalid or unknown. See issues. |
| 404 | not_found | No such resource for your account in this environment. Also returned by every ticket endpoint when ticketing is off for your account. |
| 413 | payload_too_large | The JSON body is over 1 MB (uploads have their own limits). |
| 429 | rate_limit_exceeded | See rate limits. |
| 500 | internal_error | Something failed on our side. Some endpoints add a reference_id, the same value as X-Request-Id. Safe to retry with the same idempotency key. |
| 503 | database_busy | We are briefly overloaded. Retry after Retry-After seconds. |
Signing
| Status | Code | Meaning |
|---|---|---|
| 401 | hmac_signature_missing | No X-Axiom-Signature. |
| 401 | hmac_timestamp_missing | No X-Axiom-Timestamp. |
| 401 | hmac_timestamp_invalid | The timestamp is not whole seconds. |
| 401 | hmac_timestamp_skew | More than 300 seconds from our clock. |
| 401 | hmac_partner_mismatch | X-Axiom-Partner-Slug differs from the URL. |
| 401 | hmac_partner_unknown | No account has that slug. |
| 401 | hmac_invalid | The signature does not match an active key. |
| 403 | production_not_enabled | Production is not enabled for your account yet. |
| 423 | partner_inactive | Your account is deactivated. |
| 423 | tenant_suspended | Your account is suspended. |
See Authentication and signing.
Idempotency
| Status | Code | Meaning |
|---|---|---|
| 400 | idempotency_key_required | This endpoint needs an Idempotency-Key. |
| 400 | idempotency_key_invalid | Empty, or over 128 characters. |
| 400 | idempotency_key_mismatch | Order create: header and body keys differ. |
| 409 | idempotency_key_reused | The key was used with a different body. |
| 409 | idempotency_key_in_flight | A request with this key is still running. Retry shortly. |
See Idempotency.
Orders
| Status | Code | Meaning |
|---|---|---|
| 404 | not_found / order_not_found | No such order for your account in this environment. |
| 409 | partner_order_id_duplicate | You already have a live order with this id. Carries partnerOrderId and axiomOrderId. |
| 409 | order_in_flight | The same order is being created right now (retryable: true), or, if it persists, the key belongs to an earlier failed or cancelled order (see Orders lifecycle before resubmitting). |
| 409 | order_not_editable | Only a received order can be edited. Carries status. |
| 409 | order_not_cancellable | Too late to cancel. Carries status and sometimes reason (dispatched, child_dispatched, split_child). |
| 409 | default_clinic_unavailable | The order relied on your account's default clinic, and that setting changed while the order was being placed. Nothing was created; retry. Sending clinic.external_id avoids it. |
| 422 | prescriber_incomplete | Prescriber details missing. See blockers. |
| 422 | order_incomplete | Other required details missing (for example patient.sex). See blockers. |
| 422 | unknown_product | SKU not recognised for you. Carries sku and reason. |
| 422 | product_retired | SKU retired. Carries sku and retiredAt. |
| 422 | product_not_in_catalog | SKU not in your catalog or the clinic's. Carries sku. |
| 422 | sku_not_priced | No price set up for you. Carries sku. |
| 422 | product_not_yet_available | Held until availableAt (it can still be refused after that, for example as backordered). Carries sku. |
| 422 | product_backordered | Carries sku and, when known, expectedBackAt. |
| 422 | ship_state_not_licensed | No licensed pharmacy for the ship-to state. |
| 422 | product_unavailable_in_state | The SKU cannot ship to state. |
| 422 | no_pharmacy_can_fill | No pharmacy available to this order can fill sku for state. |
| 422 | prescriber_inactive | The prescriber was removed from the clinic's provider list. Carries message. Being introduced; announced before it is switched on. |
| 422 | agreement_unsigned | The clinic's agreement is missing. Carries reason. See Clinics and agreements. |
| 422 | clinic_overdue | The clinic has an invoice past due. Carries overdueSince and graceDays. |
| 422 | partner_overdue | Your account has an invoice past due. Carries overdueSince and graceDays. |
| 422 | clinic_suspended | The clinic is suspended. |
| 422 | clinic_over_credit_cap | Over the clinic's credit limit. Carries capCents, outstandingCents, orderChargeCents, projectedCents. |
| 422 | partner_exposure_cap | Over your account's overall limit. |
| 422 | billing_account_id_unsupported | billing.account_id disagrees with clinic.external_id. Carries details.clinicExternalIdPresent. |
See Orders lifecycle.
Clinics
| Status | Code | Meaning |
|---|---|---|
| 400 | banking_invalid | A bank field failed the storage check, for example a bank or account-holder name that is only spaces. (Malformed routing or account numbers are normally a 400 validation_failed.) |
| 404 | clinic_not_found | No such clinic on your account. |
| 409 | banking_conflict | A different bank account is on file. Use PUT /clinics/banking. |
| 409 | npi_conflict | Another of your clinics has that NPI. |
| 409 | ach_debit_in_flight | A debit for the clinic is in progress. Retry later. |
| 409 | bank_revisions_off | Bank account authorization through the API is not on for this clinic yet. |
| 409 | bank_authorize_refused | Carries reason: not_newest, account_mismatch, already_authorized, rejected or approval_pending. |
| 422 | banking_requires_attestation / identity_requires_attestation | Only partner-attested accounts can use the banking endpoints and PATCH /clinics/identity (and send banking on enrollment). |
| 422 | clinic_not_attested | The clinic has no partner attestation on file. |
| 422 | axiom_boldsign_required | Your clinics sign Axiom's agreement directly; they cannot be enrolled through the API. |
| 422 | signer_identity_locked_boldsign | Signer details cannot change through the API on e-signature accounts. Carries fields. |
| 422 | clinic_not_resolved | The clinic could not be created or found. |
| 429 | bank_change_quota | One bank account change per clinic per 24 hours (where bank account revisions are kept). |
Support tickets
| Status | Code | Meaning |
|---|---|---|
| 400 | empty_file | The upload had no bytes. |
| 400 | invalid_upload_token / duplicate_upload_token | An attachment token is unknown, expired, already used, or repeated. (More than 4 attachments is a 400 validation_failed.) |
| 404 | ticket_not_found / attachment_not_found | No such ticket or attachment on your account. |
| 413 | file_too_large / payload_too_large | Over the size limit. Usually carries maxBytes; a body the server stops reading early may answer with only a message. |
| 415 | unsupported_file_type / file_content_mismatch | File type not accepted, or the bytes do not match the declared type. |
| 422 | invalid_status_transition | For example resolving a resolved ticket. Carries from, to, allowed. |
| 503 | upload_capacity | Too many uploads in progress. Retry after Retry-After seconds. |
Webhook endpoints
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | On create: a bad URL, environment or event name. See issues. (A bad URL on PATCH answers 400 with the reason written out in error.) |
| 400 | no_fields_to_update | A PATCH with nothing to change. |
| 400 | invalid_id | The endpoint id is not a number. |
| 404 | not_found | No such endpoint on your account. |
| 409 | endpoint_url_already_registered | You already registered that URL. |

