Errors and rate limits
What failures look like on the wire, which status codes mean what, and how to pace your requests.
On this page
Error shape
Errors carry a detail field. Read that; do not parse prose out of the status line.
JSON. 403
{
"detail": "Bot not found or access denied."
}Validation failures return 422 with detail as a list of field-level problems. Feature-gated endpoints return a machine-readable code alongside the message, the same shape whether the gate is a plan feature or an exhausted credit balance:
JSON. 403, feature not on this plan
{
"detail": {
"error": "feature_not_available",
"feature": "lead_intelligence",
"message": "Lead export is included on Starter and above."
}
}JSON. 402, out of credits
{
"detail": {
"error": "insufficient_credits",
"required": 1,
"available": 0,
"message": "You're out of credits. Upgrade your plan or buy a top-up to keep chatting."
}
}Status codes
| Code | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success. | - |
| 202 | Accepted. Long-running work was queued. | Poll the matching status endpoint. |
| 400 | Malformed request. | Fix the request. Retrying will not help. |
| 401 | Missing or invalid key. | Check the header name and the key value. |
| 402 | Out of credits. detail.error is insufficient_credits, with required and available. | Top up or upgrade. Retrying will not help. |
| 403 | Authenticated but not allowed. Wrong workspace, insufficient role, or a plan gate. | Read detail. Do not retry. |
| 404 | No such resource, or not yours. | Do not retry. |
| 409 | Conflicts with current state. | Re-read the resource and decide. |
| 422 | Validation failed. | Fix the fields listed in detail. |
| 429 | Rate limited. | Wait for Retry-After seconds, then retry. |
| 5xx | Something failed on our side. | Retry with exponential backoff. |
Rate limits
Limits are per endpoint and are counted against your API key, your bot key, or the caller's IP depending on the endpoint. Expensive operations are limited harder than reads.
| Endpoint class | Typical limit |
|---|---|
| Reads (lists, analytics, settings) | 20 to 60 per minute |
| Chat, including streaming | 30 per minute per bot key |
Document upload. POST /ingest | 10 per minute |
| Upload cost preview | 20 per minute |
Start a crawl. POST /crawl | 10 per hour |
| Crawl discovery | 120 per hour |
| Authentication and password reset | 5 to 10 per minute |
| Billing and checkout | Tightly limited; see the response headers |
The exact ceiling is not published in the response. A 429 body does not restate the configured limit. Publishing it would tell an abusive caller exactly how slowly to grind. What you get instead is a
Retry-After header in seconds and a retry_after_seconds field in the body, which is what a well-behaved client actually needs.JSON. 429
{
"detail": "Too many requests. Please slow down and try again shortly.",
"error": "Rate limit exceeded",
"retry_after_seconds": 24
}Retrying well
- Honour
Retry-Afteron a 429 rather than guessing a backoff. - Retry 5xx with exponential backoff and jitter. Do not retry 4xx other than 429.
- Keep write requests idempotent on your side, so a retry after a timeout cannot double-apply.
- Prefer webhooks over polling. A webhook costs you nothing against your rate limits and arrives sooner.
Something here wrong or missing? Tell us and name this page. We will fix it.