Skip to main content
Errors are JSON. The HTTP status is authoritative; the error code is the stable, machine-readable name to branch on. Human-readable message strings may change without notice — never match on them.

Envelope

{
  "error": "invalid_payload",
  "message": "Request body failed validation.",
  "request_id": "req_8fK2x9aLp0qR",
  "details": {
    "formErrors": [],
    "fieldErrors": {
      "name": ["Required"],
      "status": ["Invalid enum value. Expected 'draft' | 'active', got 'live'."]
    }
  }
}
  • error — stable machine code. Branch on this.
  • message — human-readable. For logs and UI; do not parse.
  • request_id — echo of the X-Request-Id response header. Include it in support tickets.
  • details — present when the error has structured context (validation field errors, conflicting state, etc.). Shape varies per code.
For validation failures (invalid_payload, invalid_query), details is { "formErrors": [...], "fieldErrors": { "<field>": ["message", ...] } }. fieldErrors keys are the offending fields, each mapped to an array of messages; formErrors holds errors that aren’t tied to a single field.
Timestamps throughout the API are ISO-8601 with an explicit +00:00 UTC offset (for example "2026-06-09T22:50:58.806+00:00"), not a trailing Z.
Always log request_id and the full response body when a request fails. The request id lets support trace a single call across the stack in under a second.

Authentication

StatusCodeWhen you see itRecovery
401missing_api_keyNo Authorization header on the request.Add Authorization: Bearer aw_live_….
401invalid_api_keyHeader present, value not recognized.Typo, trailing whitespace, or wrong environment. Re-check the value.
401api_key_revokedKey was revoked in the dashboard.Mint a fresh key.
401api_key_expiredKey passed its expires_at.Mint a fresh key or extend the expiration.
403api_key_pausedKey exists but is paused.Resume it in the dashboard.
403forbiddenAction not permitted for this key. Typically a cross-org access attempt.Check that the resource belongs to the same org as the key.
See Authentication for key lifecycle.

Validation

StatusCodeWhen you see itRecovery
400invalid_jsonRequest body wasn’t valid JSON.Fix the JSON — usually a trailing comma, unquoted key, or wrong content-type.
400invalid_payloadBody parsed but failed schema validation. details.fieldErrors maps each field to its messages.Fix the listed fields.
400invalid_queryQuery string failed validation. details.fieldErrors maps each field to its messages.Fix the query parameters.
400invalid_idA path or query id isn’t a valid UUID.Awardee ids are bare UUIDs. No prefixes.
400invalid_cursorPagination cursor is malformed or tampered.Pass next_cursor verbatim from the prior response, URL-encoded.
422unprocessableWell-formed request that cannot be applied — for example, referencing a resource that doesn’t exist or belongs to another org (message: “A referenced resource does not exist.”).Fix the offending reference.
422insufficient_creditsThe action would consume credits the org doesn’t have.Top up credits, or remove the credit-consuming feature flag from the request.

Not found and gone

StatusCodeWhen you see itRecovery
404not_foundResource doesn’t exist, or it belongs to a different org.Confirm the id and the key’s org. Cross-org access returns 404 by design, not 403.
410goneResource existed but has been permanently deleted. The id will never resolve again.Stop referencing it.

Conflict

StatusCodeWhen you see itRecovery
409conflictGeneric state conflict — e.g. updating a resource that was modified concurrently, or violating a uniqueness constraint. details describes the conflict.Re-fetch the resource and retry with the latest state.
409category_not_emptyDeleting an article category that still has child categories or articles.Move the children first, or pass ?cascade=true to delete them too.
409idempotency_conflictIdempotency-Key reused with a different request body inside the 24h window.Either generate a new key, or send the same body. See Idempotency.

Rate limits

StatusCodeWhen you see itRecovery
429rate_limitedThe organization’s minute or hour cap was hit (one shared bucket across all credentials).Honor the Retry-After header, add jitter, back off exponentially. See Rate limits.

Server errors

StatusCodeWhen you see itRecovery
500internal_errorUnhandled exception on our side. Always a bug.Safe to retry. If it persists, contact support with the request_id.
503service_unavailableUpstream dependency (database, queue, AI provider) is degraded.Retry with exponential backoff. Our status page reflects sustained incidents.
5xx responses are always retry-safe when paired with an Idempotency-Key on POSTs. GET, PATCH, PUT, and DELETE are naturally idempotent.

Worked examples

A validation error on POST /chatbots:
POST /v1/chatbots HTTP/1.1
Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e
Content-Type: application/json

{ "status": "live" }
HTTP/1.1 400 Bad Request
X-Request-Id: req_8fK2x9aLp0qR
Content-Type: application/json

{
  "error": "invalid_payload",
  "message": "Request body failed validation.",
  "request_id": "req_8fK2x9aLp0qR",
  "details": {
    "formErrors": [],
    "fieldErrors": {
      "name": ["Required"],
      "status": ["Invalid enum value. Expected 'draft' | 'active' | 'paused' | 'archived', got 'live'."]
    }
  }
}
A cross-org access attempt:
GET /v1/chatbots/1b4c4f5d-9e8a-7c3b-2a1d-0f9e8f3a9d2e HTTP/1.1
Authorization: Bearer aw_live_4f8a3c7e2d1b9a5c6f8e3d2c1b4a9f7e
HTTP/1.1 404 Not Found

{
  "error": "not_found",
  "message": "Chatbot not found.",
  "request_id": "req_2c1b4a9f7e8d"
}
The resource exists, but not under your org. We return 404 rather than 403 to avoid leaking the existence of resources in other orgs.