Errors
The API reports errors with standard HTTP status codes and a JSON body. Decide what to do from the status code first, then from the machine-readable code in the body. Never parse the human-readable message: its wording can change.
Status codes
| Status | Meaning | What to do |
|---|---|---|
400 |
The request was understood but its content is not acceptable: a missing or invalid field, a field you may not change. | Fix the request. Retrying it unchanged will fail again. |
401 or 403 |
The key is missing, malformed, unknown or revoked; or the key is valid but not allowed here; or your plan does not include the action. | Read the code (below) to tell these apart. |
404 |
The object does not exist, or it belongs to another account. Also returned for a page number past the end of a list. | Check the ID. Horizon does not tell you whether another account’s object exists. |
409 |
The request conflicts with the object’s current state. | Read the error_code. |
429 |
Too many requests (throttled). |
Wait for Retry-After seconds and retry. |
5xx |
Something failed on Horizon’s side. | Retry with backoff. |
A rejected key can come back as 401 or 403 depending on the endpoint. Do not branch on the status alone for authentication problems; use the code.
Body shapes
There is not one envelope for every error. Expect one of these.
A general refusal carries a message in detail, and often a code:
{ "detail": "Invalid or inactive API key.", "code": "api_key_invalid"}A validation error names each field that failed, with a list of messages, and uses non_field_errors for a problem that is not about one field. For example:
{ "quantity": ["A valid integer is required."]}An order refusal carries an error_code in capitals, a detail message, and extra keys that depend on the code. Validation-style messages for the fields involved sit under each field’s name, as above:
{ "error_code": "READ_ONLY_FIELDS", "detail": "These fields are kept by Horizon and cannot be changed through the API: status.", "read_only_fields": ["status"], "status": ["…"]}A robust client reads code, then error_code, and falls back to the status code when neither is present.
Codes worth handling
Authentication
| Code | Meaning |
|---|---|
api_key_header_invalid |
The Authorization header starts with Api-Key but is not Api-Key <key>. |
api_key_invalid |
The key is unknown or has been revoked. Do not retry; check which key is deployed. |
api_key_owner_inactive |
The account the key belongs to is no longer active. |
api_key_forbidden |
The key is valid, but it was used outside the documented API, for example to buy a label or change an order. Keys work only on the endpoints in this section. See Authentication and keys. |
Plan and billing
| Status | Code | Meaning |
|---|---|---|
403 |
subscription_required |
The account has no active subscription that covers this action. |
403 |
dimension_unavailable |
Your plan does not include this at all. |
403 |
grace_restricted |
A payment on the account failed. Some actions are paused until the payment details are updated. |
403 |
entitlement_lookup_failed |
Horizon could not work out your plan just then. Nothing was done. This is on Horizon’s side; try again later. |
When a higher plan would lift the limit, the body also carries upgrade_to with the plan’s plan_key and plan_name. Your allowances and current usage are in the app; see Usage and limits.
Orders
| Status | Code | Meaning |
|---|---|---|
400 |
READ_ONLY_FIELDS |
A new order set fields Horizon keeps. read_only_fields lists them. Nothing was saved. |
400 |
ADDRESS_INVALID |
A delivery address field of a new order fails the destination country’s format. fields gives a code per field. |
API keys lists the fields Horizon keeps and the address codes.
Rate limiting
A 429 body carries "code": "throttled" and retry_after, the number of seconds to wait, which is also sent in the Retry-After header. retry_after can be null when no wait is known; back off before retrying anyway.