Errors and limits
Every non-2xx response uses the same envelope. The request_id matches the X-Request-ID header, and it is what makes a specific failure traceable.
The envelope
{
"error": {
"code": "not_found",
"message": "Resource not found",
"request_id": "b3f1c2a4-..."
}
}Validation errors return 422 with code "validation_error". The message lists the offending fields, as field: reason.
{
"error": {
"code": "validation_error",
"message": "body.email: value is not a valid email address; body.name: field required",
"request_id": "b3f1c2a4-..."
}
}| Status | Code | When it shows up |
|---|---|---|
| 401 | unauthorized | Token missing, invalid or expired. |
| 403 | forbidden | Authenticated, but not allowed to do that. |
| 404 | not_found | It does not exist, or you are not a member of its organization. |
| 409 | conflict | State conflict: a duplicated unique value, for instance. |
| 422 | validation_error | The body or the parameters fail validation. |
| 429 | too_many_requests | You went over the limit. Retry later. |
Usage limits
The limiter is fail-open: if the service that counts them is unavailable, the request is not blocked — availability wins. Even so, design your clients to retry with exponential backoff on a 429, and avoid bursts you do not need.
Quotas for calls made with a token
On top of the per-IP ceiling, calls made with a token (or over OAuth from an assistant) count against their own quotas, per person and per organization. These are the server defaults.
| Quota | Limit | Counted per |
|---|---|---|
| Reads | 120 per minute | actor and person, in each organization |
| Writes | 20 per minute | actor and person, in each organization |
| Financial operations that modify data | 5 per hour | actor and person, in each organization |
| Whole organization | 600 reads, 120 writes per minute and 30 financial per hour | all of the organization’s agents together |
| Global agent ceiling | 300 requests per minute | person, across all their organizations |
| Opening streams (SSE) | 60 per minute | person, agent or IP |
Minting more tokens does not buy more quota: the person is counted too. These figures are tuned under real load, so do not hardcode them: read the Retry-After header.
What to do on a 429
A 429 carries a Retry-After header with the seconds left until another request fits. Wait that long, not a blind full window, and retry a few times. The API does not send X-RateLimit-* headers: the 429 itself is the signal.
async function call(url, token, attempts = 4) {
for (let i = 0; i < attempts; i++) {
const r = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
if (r.status !== 429) return r;
const wait = Number(r.headers.get('Retry-After')) || 2 ** i;
await new Promise((ok) => setTimeout(ok, wait * 1000));
}
throw new Error('Still rate limited after several attempts');
}