Errors
The Bookbag error envelope and the common machine-readable codes. How v2 and the Chatbase-compatible v1 surfaces report failures, plus rate limiting.
View as MarkdownBookbag uses conventional HTTP status codes and a consistent JSON envelope so you can branch on a stable, machine-readable code rather than parsing prose.
The v2 error envelope
Every API v2 error response has this shape:
{
"error": {
"code": "VALIDATION_INVALID_BODY",
"message": "message is required and must be a non-empty string",
"details": {}
}
}code— a stable, screaming-snake-case identifier. Branch on this.message— a human-readable explanation. May change; do not match on it.details— present only on some errors (for example field-level validation issues).
The HTTP status reflects the class of error; the code gives the specific reason.
Common codes
| Status | Code | When |
|---|---|---|
| 400 | VALIDATION_INVALID_BODY | A required field is missing or a field failed validation (e.g. empty message, malformed userId). |
| 401 | AUTH_MISSING_API_KEY | No Authorization: Bearer header. |
| 401 | AUTH_INVALID_API_KEY | Unknown, malformed, or revoked key. |
| 403 | AUTH_INSUFFICIENT_PERMISSIONS | An agent-scoped key targeting a different agent. |
| 404 | RESOURCE_NOT_FOUND | The agent or conversation does not exist or is not in your workspace. |
| 404 | RESOURCE_MESSAGE_NOT_FOUND | The referenced message does not exist in the conversation. |
| 404 | RESOURCE_TOOL_CALL_NOT_FOUND | No pending tool call matched the supplied toolCallId. |
| 400 | RESOURCE_MESSAGE_NOT_ASSISTANT | Feedback was sent for a non-assistant message. |
| 400 | CHAT_RETRY_NO_USER_MESSAGE | A retry was requested but there is no user turn to retry from. |
| 404 | CHAT_RETRY_MESSAGE_NOT_FOUND | The messageId to retry was not found in the conversation. |
| 429 | RATE_LIMIT_TOO_MANY_REQUESTS | The per-key rate limit was exceeded. |
| 500 | INTERNAL_SERVER_ERROR | An unexpected server error. Safe to retry with backoff. |
Rate limiting
API v2 is rate limited per API key using a sliding window of 100 requests per 10 seconds. Every v2 response carries the current limit state in headers:
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | The window ceiling (100). |
| X-RateLimit-Remaining | Requests left in the current window. |
| X-RateLimit-Reset | Unix-ms timestamp when the window resets. |
When the limit is exceeded you receive 429 with code RATE_LIMIT_TOO_MANY_REQUESTS. Back off (roughly one second) and retry.
Treat 429 and 5xx as retryable. Retry with exponential backoff and a little jitter; do not retry 4xx validation or auth errors — fix the request instead.
The v1 (Chatbase-compatible) error body
The Chatbase-parity contacts and custom-attributes endpoints under /api/v1/chatbots/... use the conventional status codes but a simpler body containing only a message:
{ "message": "Resource not found" }| Status | Example message | When |
|---|---|---|
| 400 | Invalid request data | Missing or malformed body (e.g. no users array on create). |
| 401 | No API key provided. / Invalid API key. | Missing or invalid Bearer key. |
| 404 | Resource not found | Unknown chatbot/contact/attribute, or out of your workspace. |
| 409 | External ID or email already exists. | A uniqueness conflict on external_id. |
The legacy v1 chat stream (POST /api/v1/chat) reports validation and not-found errors as JSON { success: false, error: { code, message } } before the SSE stream opens. See Chat with an agent (v1).