Errors

Response shape

Every error response has the same minimal shape — both validation failures (400) and authorisation failures (401, 429). Internal errors (5xx) are never exposed with stack traces.

// Single-message variant
{ "statusCode": 401, "message": "Invalid credentials" }

// Validation variant (class-validator)
{
  "statusCode": 400,
  "message": [
    "email must be an email",
    "property foo should not exist"
  ]
}

Status codes you can encounter

  • 400 Bad Request — body validation failed. Inspect the returned message[].
  • 401 Unauthorized — see Authentication. Always generic.
  • 402 Payment Required{"error":"quota_exceeded", ...} from /v1/messages when the monthly quota is exhausted.
  • 404 Not Found — unknown endpoint, or /v1/messages/:id for a non-existent message: {"statusCode":404,"message":"Message not found"}.
  • 410 Gone — full-account confirmation/agreement link expired, or registration was rejected.
  • 413 Payload Too Large — request body exceeds endpoint cap (64 KB on /v1/signup, 1 MB elsewhere). Returned by nginx as plain HTML, not the JSON shape above.
  • 429 Too Many Requests — rate limit hit; see Limits & Restrictions.

What we deliberately do NOT return

  • Distinct error messages for missing vs invalid vs revoked bearer (anti-enumeration).
  • X-RateLimit-* headers (anti-calibration).
  • Stack traces or internal field names from validation libraries beyond the message string.
  • Server: nginx/<version>server_tokens off on all public endpoints.