Errors
Errors
Failed requests return a non-2xx status and a JSON body with a statusCode and a readable
message:
{ "statusCode": 403, "message": "API key is missing scope(s): agents:read", "error": "Forbidden" }
Branch on the status code. Messages are for people and may be reworded.
Authentication and permission errors
| Status | Message | Cause and fix |
|---|---|---|
401 | Missing auth token | No Authorization header. Add Authorization: Bearer <key>. |
401 | Invalid or expired token | The value isn't a BetterClaw key at all, often a typo or a missing Bearer prefix. |
401 | Invalid API key | The key was revoked, or never existed. Generate a new one. |
401 | The user this API key acts as no longer has access to the workspace | The key's creator left the workspace. Revoke it and generate a new one. |
401 | Invalid or expired SDK token | The session token expired or was altered. Mint a new one. |
401 | An API key must be sent in the Authorization header, not a query parameter | Move the key out of the URL. |
403 | An API key must not be used from a browser — exchange it for a session token | The request had an Origin header. Use a session token in browsers. |
403 | This endpoint cannot be used with an API key | The route isn't part of the Agent API, for example managing keys or billing. Use the app. |
403 | API key is missing scope(s): … | Generate a key (or mint a token) with the listed permission. |
403 | This API key is not bound to that workspace | The request names a different workspace than the key's. |
403 | This API key is pinned to a different agent | The request names an agent other than the one the key is pinned to. |
404 | Not found | The chat belongs to another workspace, or to an agent the key isn't pinned to. |
Revoking a key takes effect on its very next request, and on every session token minted from it.
Request and resource errors
| Status | When |
|---|---|
400 | A required field or query parameter is missing, or a message has neither text nor files. |
402 | The workspace is out of credits for the month, payment is required to resume it, or the agent is locked on the Free plan. |
404 | The chat, message, or deliverable doesn't exist, or was deleted. |
409 | A clientRequestId was reused with different content. |
410 | A retried request refers to a chat that has since been deleted. |
413 | An attachment is over 30 MB, or the files in one request are too large together. |
415 | An attachment's type isn't accepted. |
422 | The body failed validation; the response lists each problem in issues. (POST /chats answers 400 instead.) |
429 | Too many failed token exchanges from your IP address. Wait retryAfter seconds. |
5xx | Something went wrong on our side. Retry with a back-off, using a clientRequestId for sends. |
A 402 for a workspace that needs payment includes a code:
{
"statusCode": 402,
"code": "BILLING_REQUIRED",
"workspaceId": "6f1c2d4e-8a90-4b1c-9d2e-3f4a5b6c7d8e",
"message": "Payment is required to resume this workspace."
}
A 422 lists each invalid field:
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Validation failed",
"issues": [{ "path": "ttlSeconds", "message": "Number must be less than or equal to 900" }]
}
Replies that fail
A reply can fail after POST /chats/{chatId}/messages succeeded, for example because the agent
couldn't start or its model provider returned an error. The request itself still returns 201; the
assistant message then ends with status: "error" and an errorMessage. Check for it when you
read the reply or in the
stream.
Stream close codes
WebSocket failures arrive as close codes rather than HTTP statuses. See Close codes.