Rate limits & errors
The API protects itself with two rate limits. Both apply to every request.
Limits
| Limit | Default | Configurable up to | Scope |
|---|---|---|---|
| Per-API-key requests | 60 / minute | 10,000 / minute | Set per key when the key is issued. |
| Per-IP requests | 600 / minute | Not configurable | Applied across all keys from a given IP. |
A request that breaches either limit is rejected with 429 rate_limited.
The default per-key budget is intentionally low. If your integration sustains higher throughput, raise the per-key ceiling at issuance rather than spreading load across multiple keys — the per-IP cap will catch you anyway.
Headers
Every successful response carries the per-key window state:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The current per-key budget. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix epoch seconds when the current window resets. |
A 429 response also carries Retry-After, in seconds. Honor it. Do not retry sooner than the value advertises — doing so deepens the throttle, it does not bypass it.
Error envelope
All non-2xx responses share one shape:
{
"error": {
"code": "validation_error",
"message": "displayName is required.",
"details": {
"field": "displayName"
}
}
}
The fields:
code— a stable, machine-readable identifier. Switch on this, not onmessage.message— a human-readable summary. May change between versions.details— optional, structured. Shape depends on the code. Absent when there's nothing useful to add.
Canonical error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | validation_error | Request body or parameters failed schema validation. |
| 401 | invalid_token | Bearer token is missing, malformed, expired, or revoked. |
| 403 | insufficient_scope | Token is valid but lacks a scope the endpoint requires. |
| 404 | <resource>_not_found | The named resource does not exist or is not visible to this token. The prefix is the resource name (contact_not_found, deal_not_found, etc.). |
| 429 | rate_limited | Per-key or per-IP budget exhausted. Honor Retry-After. |
<resource>_not_found is intentionally indistinguishable between "doesn't exist" and "exists but not visible to you" — the API does not leak the existence of resources outside the token's tenancy.
Retry guidance
- Idempotent reads (
GET): retry on 429 and on 5xx, with backoff. Cap retries. - Writes (
POST,PATCH,DELETE): retry only on 429 and on connection-level failures, never on a non-429 4xx. A 4xx other than 429 means the request will not succeed if repeated. - Honor
Retry-Afterwhen present; otherwise use exponential backoff with jitter.