Errors
The consistent error envelope and its validation details.
The error envelope
Every error — validation, auth, rate-limit, or server — comes back in one consistent shape. Branch on the machine-readable code, and surface message for humans:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request body is invalid",
"details": [ /* optional, structured — see below */ ]
}
}
The HTTP status still carries meaning (401 unauthenticated, 403 forbidden, 404 not found, 422 unprocessable, 429 rate-limited, 5xx server), but code is the stable value to switch on — messages may be reworded. See the error codes reference for the full list.
Validation details
A 422 validation failure includes a details array. Each entry is normalised to a path, a code, and a message, so you can map failures back to the offending field regardless of the validator version:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request body is invalid",
"details": [
{ "path": "name", "code": "too_small", "message": "String must contain at least 1 character(s)" },
{ "path": "attributes.netWeight", "code": "invalid_type", "message": "Expected number, received string" }
]
}
}
Request IDs
Every response carries an X-Request-ID header. Log it and include it when contacting support — it lets us trace a single request end-to-end through the platform.
Always branch on error.code, never on error.message. Codes are part of the API contract; messages are for display and may change.