Errors
The atoship API uses conventional HTTP response codes to indicate the success or failure of an API request. Codes in the 2xx range indicate success, 4xx indicate client errors, and 5xx indicate server errors.
HTTP Status Codes
Success Codes
Request succeeded
Resource created successfully
Client Errors
Invalid request parameters
Invalid or missing API key
Insufficient balance or payment failed
API key lacks required permissions
Resource does not exist
Validation error on request data
Rate limit exceeded
Server Errors
Something went wrong on our end
API is temporarily unavailable
Error Object
Every error returns a JSON body with an error object. Branch on the HTTP status code and error.code — those two are on every error response.
error.codestringMachine-readable code in UPPER_SNAKE_CASE (e.g. "RATE_LIMIT_EXCEEDED"). This is the field to branch on.
error.messagestringHuman-readable description. Safe to show a user; do not parse it — the wording changes.
error.detailsarray (optional)Validator output on a 400. Each entry carries path and message; the shape follows the field that failed.
error.paramstring (optional)The offending field, when a single one can be named (e.g. "to_address.phone").
error.retryAfternumber (optional)Seconds to wait, on a 429 only. The same value is in the Retry-After header.
Responses also carry object, and some carry mode. Neither is guaranteed on every endpoint, so do not branch on them. There is no type or request_id field.
Handling Errors
Use the status code to determine the error category
The UPPER_SNAKE_CASE code is the stable handle — match on it, not on the message text
Show the message to users for actionable feedback
Wait error.retryAfter seconds (or read the Retry-After header) before retrying
| 1 | { |
| 2 | "object": "Error", |
| 3 | "error": { |
| 4 | "code": "VALIDATION_ERROR", |
| 5 | "message": "Invalid request data", |
| 6 | "details": [ |
| 7 | { |
| 8 | "code": "custom", |
| 9 | "path": ["parcel"], |
| 10 | "message": "Send `parcel` for a single box, or `parcels` for a multi-piece shipment." |
| 11 | } |
| 12 | ] |
| 13 | } |
| 14 | } |
Endpoints add their own codes — a label purchase can return DESTINATION_PHONE_REQUIRED or CUSTOMS_REQUIRED. Treat an unrecognised code as its HTTP status class.
The budget belongs to your organization, not to a key — every API key and user on the account draws from the same one.
Counted in fixed windows. The daily budget resets at 00:00 UTC, and a 429 carries a Retry-After header.
Need a higher limit? Contact [email protected] — limits are raised per account.
