Skip to main content

The error envelope

Every error response from /api/v1/ is a JSON object with a single top-level error key whose value is a human-readable string:
This is consistent across authentication failures, permission checks, throttling, not-found, and method-not-allowed responses — you can always read body.error for a displayable message.

Machine-readable codes

A few errors you are expected to branch on also carry a code next to error. Match on code, never on the message text, which may be reworded:
Errors without a listed code have no code key. A monthly_limit_exceeded refusal also carries the numbers behind it, so you can narrow the audience or prompt an upgrade without another request:
A broadcast either fits in what’s left or isn’t sent at all; it is never sent in part. The monthly quota stops bulk sends only (broadcasts and sequences). Replies, including POST /api/v1/messages/, keep working at the cap and still count toward usage.

Status codes

Validation errors

Field-level validation errors (400 Bad Request) are the one intentional exception to the single-error envelope. They follow Django REST Framework’s field-keyed convention — a map of field name to a list of messages, so you can surface errors next to the right input:
Non-field errors appear under non_field_errors:
When parsing a 400, check for the error key first; if it’s absent, treat the body as a field → messages map.

Server errors

A 500 never leaks internal detail. The full exception (including stack trace) is captured server-side; the client always receives a fixed, parsable body:
If you hit a reproducible 500, contact support with the timestamp and endpoint — we can correlate it to the captured exception. A request to an unrouted /api/v1/... path (a typo’d or non-existent endpoint) returns 404 with the same envelope, {"error": "Not found"}.