Errors

HTTP status codes, error envelope, and error codes returned by the API.

Error envelope

Most errors return a JSON error object with a machine‑readable code, a human‑readable message, and an optional details array, alongside the request meta:

{
  "error": {
    "code": "agent_unavailable",
    "message": "Agent is currently disabled.",
    "details": []
  },
  "meta": {
    "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f"
  }
}

A few failures bypass the envelope. Branch on the HTTP status code first, then read error.code when the body carries an error object:

  • 401 Unauthenticated — a missing, malformed, revoked, or expired bearer key: { "message": "Unauthenticated." }.
  • 403 / 404 from ownership or route binding — a resource you don't own, or a UUID that doesn't resolve: { "message": "This action is unauthorized." }.

Everything else uses the envelope, including validation (422 validation_failed), a missing scope, an expired key, and a missing premium plan.

Validation errors

When a request body or query parameter fails validation, the API returns 422 with the standard envelope, code: "validation_failed", and a details array naming each invalid field:

{
  "error": {
    "code": "validation_failed",
    "message": "The content field is required.",
    "details": [
      { "field": "content", "message": "The content field is required." }
    ]
  },
  "meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}

message repeats the first field error for convenience; iterate details — each entry is { field, message } — to surface every problem at once.

This is not Laravel's default { "message", "errors": { "field": [...] } } validation shape. Read error.details[].field and error.details[].message.

Status codes

StatusMeaning
200 OKRequest succeeded.
201 CreatedResource created.
202 AcceptedAsynchronous job accepted (crawl started).
204 No ContentSucceeded with no body (delete).
401 UnauthorizedMissing/invalid key, or the key has expired.
403 ForbiddenNot the owner, missing scope, or no premium plan.
404 Not FoundThe agent, conversation, message, webhook endpoint, source, or training job does not exist, or it does not belong to the agent in the path. Visitor send and action‑result also return 404 when the conversation was not created through this API. A resource that exists but you don't own returns 403, not 404.
409 ConflictAn open public API conversation already exists for this visitor (conversation_conflict).
422 Unprocessable EntityValidation failed, the conversation is closed, or a subscription limit was reached.
423 LockedThe agent is disabled and cannot accept the operation.
429 Too Many RequestsRate limit exceeded — see Rate Limits.
502 Bad GatewayAn upstream dependency (storage or the training service) failed.

Error codes

These appear as error.code in the envelope:

codeStatusMeaning
token_expired401The API key passed its expiry date.
unauthorized401Visitor identity_token signature is invalid, or aud does not match the agent. Expired, malformed, or incomplete tokens are 422 validation_failed.
premium_required403Organization lacks an active premium/AppSumo plan.
insufficient_scope403The key is missing the scope this endpoint requires.
forbidden403The key's user has no organization, or is not the organization owner.
resource_not_found404The conversation, message, webhook endpoint, source, or training job was not found for this agent — or visitor send / action‑result targeted a conversation that was not created through this API.
conversation_conflict409This chat_user_id already has an open public API conversation on the agent. Widget/email threads do not count. details includes conversation_id.
validation_failed422A request body or query parameter failed validation, including a missing or invalid identity_token.
conversation_closed422The conversation is closed (visitor send, or PATCH response_mode on a closed thread).
subscription_limit_reached422An agent‑count limit on the plan was reached.
subscription_limit_exceeded422A training quota was exceeded, or a visitor send that would dispatch AI hit the message credit cap. Conversation message is always The organization has reached its message limit.
premium_plan_required422 or 403A premium‑only model or setting was requested (422 on agents/sources). Conversation PATCH and human reply map the same case to 403.
agent_unavailable423The agent is disabled. Message is Agent is currently disabled.
upstream_failure502Storage upload, crawl, retrain, or status lookup failed upstream.

Examples

403 insufficient_scope

{
  "error": {
    "code": "insufficient_scope",
    "message": "Token is missing required scope: source:create",
    "details": []
  },
  "meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}

409 conversation_conflict

{
  "error": {
    "code": "conversation_conflict",
    "message": "An open public API conversation already exists for this visitor.",
    "details": [
      { "field": "chat_user_id", "conversation_id": "3f1a9c2e-8b4d-4e6a-9c1f-2a3b4c5d6e7f" }
    ]
  },
  "meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}

422 conversation_closed

{
  "error": {
    "code": "conversation_closed",
    "message": "This conversation is closed.",
    "details": []
  },
  "meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}

423 agent_unavailable

{
  "error": {
    "code": "agent_unavailable",
    "message": "Agent is currently disabled.",
    "details": []
  },
  "meta": { "request_id": "b7f0b9c2-1a2b-4c3d-8e9f-0a1b2c3d4e5f" }
}

Include the X-Request-ID response header (also in meta.request_id) when contacting support — it lets us trace the exact request.