Errors come back as JSON with a message field. Validation errors add an errors array. The 401 Unauthorized response is plain text — code your client to handle that one specially.
#Standard error shape
{ "message": "<short reason>" }
That's the shape returned by 400, 404, and 500 responses across every route.
400 Bad Request responses on routes with body validation also include an errors array of validator issue messages, one per failed field:
{
"message": "Invalid request body",
"errors": [
"shipTo.email: Invalid email",
"shipTo.zip: String must contain at least 1 character(s)",
"products: Array must contain at least 1 element(s)"
]
}
The errors array is missing on 400 responses that aren't body-validation failures (for example, "order already shipped" on cancel).
#Status code reference
| Code | Meaning |
| --- | --- |
| 200 OK | Successful read or update. |
| 201 Created | Successful create. Note: POST /v1/orders/cancel also returns 201 today even though it's a state change rather than a creation. Don't read too much into the code — read message. |
| 400 Bad Request | Validation failed, malformed body, or the operation isn't allowed in the current state of the resource. |
| 401 Unauthorized | Missing, malformed, or invalid API key. Plain-text body, not JSON. |
| 404 Not Found | The requested resource id doesn't exist on your account. Currently emitted only by GET /v1/orders/{orderId}. |
| 429 Too Many Requests | You've exceeded the request budget. See Rate limits. |
| 500 Internal Server Error | An unhandled error on our side. The message is short and may vary in capitalization (Internal server error, Internal Server Error, or Request failed) — read message regardless. |
#401 specifics
The 401 body is the literal string Unauthorized, not JSON:
HTTP/1.1 401 Unauthorized
Content-Type: text/html; charset=utf-8
Unauthorized
If your JSON parser throws on this response, you're hitting auth, not a malformed payload. The auth middleware short-circuits before any handler runs, so the consistent response shape used elsewhere doesn't apply.
The trigger is one of:
- The
Authorizationheader is missing. - The scheme is not
Bearer. - The key portion is empty.
- The key is not 96 characters.
- The key is well-formed but not recognized.
See Authentication and API keys for full guidance.
#404 specifics
The only route that emits 404 is GET /v1/orders/{orderId}. List endpoints return an empty array (or empty items for inventory) rather than a 404 when nothing matches.
{ "message": "Order not found" }
#500 specifics
The capitalization of the message on 500 responses varies — your client should not pattern-match on the exact string. Always read message regardless of case, or fall back to the status code for branching logic.
#Retries and duplicates
There is no Idempotency-Key header on the API today.
That has a real consequence on POST /v1/orders/create and POST /v1/contacts/create: if you retry a request because of a network timeout or a 500, and the original attempt actually succeeded server-side, you can end up with two orders or two contacts.
Until idempotency keys are available, the safe pattern is:
- Generate a client-side request id and store it before the call.
- On a timeout or
500, retry with backoff for transient errors only after checking whether the resource was created. For orders,GET /v1/orders/listfiltered bycampaignIdand the recipient's email is enough to detect a duplicate before re-sending. - Treat
400and401as terminal — retrying won't help, the request is broken. - Treat
429as a wait, not a retry — waitRateLimit-Resetseconds before the next call.