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{"code":"insufficient_balance", required, balance, topUpUrl} on paid campaign operations (render/audit/send) when the account balance cannot cover the FULL batch. Nothing is partially executed. See Billing & BYOC.
  • 403 Forbidden — e.g. self-signup workspace over its 50MB database quota: writes are blocked until space is freed.
  • 409 Conflict{"code":"audit_blocked"}: the campaign exhausted its audit-iteration limit and awaits manual moderation.
  • 404 Not Found — unknown endpoint, or /api/dialogs/:id for a non-existent dialog.
  • 410 Gone — email-confirmation link expired.
  • 413 Payload Too Large — request body exceeds the endpoint cap. 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.