Rate limits & errors¶
Rate limits¶
API requests are rate-limited per API key, using a sliding 60-second window backed by Redis.
| Plan | Requests per minute |
|---|---|
| Growth | 60 |
| Enterprise | 300 |
Each request to any /api/v1/* endpoint counts as one unit. There's no separate read vs write quota.
When you hit the limit¶
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 23
{"error": "rate_limited", "message": "Rate limit exceeded. Retry after 23 seconds."}
The Retry-After header tells you how many seconds to wait before the next request will succeed. Honour it.
Recommended client behaviour¶
- Honour
Retry-After. Sleep at least that many seconds before retrying. - Cap concurrency. A single key with 60 req/min can sustain ~1 req/second; running 10 parallel workers against the same key will hit the limit immediately.
- Use multiple keys if you have multiple consumers. One key per consumer makes it obvious which workload is misbehaving and lets you revoke without breaking the others.
- Page in larger chunks rather than making many small requests.
per_page=200is the max; use it.
Example back-off in Python¶
import time
import httpx
def get_with_backoff(client: httpx.Client, url: str, **kwargs):
for attempt in range(5):
r = client.get(url, **kwargs)
if r.status_code == 429:
wait = int(r.headers.get("Retry-After", "5"))
time.sleep(wait)
continue
r.raise_for_status()
return r
raise RuntimeError(f"Gave up after 5 attempts on {url}")
Error responses¶
All errors return JSON in a consistent shape:
{"error": "code", "message": "human-readable explanation"}
The error field is a stable machine code suitable for branching on. The message is for humans and may change wording over time.
Status codes you should expect¶
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad input — invalid filter value, malformed date, etc. |
| 401 | Missing or invalid Authorization header |
| 403 | Authenticated but not allowed (e.g. plan doesn't include API access) |
| 404 | Resource doesn't exist or belongs to another account |
| 422 | Validation error (e.g. note too long) |
| 429 | Rate-limited |
| 500 | Our server error — retry with backoff; check the status page |
| 502 / 503 / 504 | Transient — retry with backoff |
Common error codes¶
| Code | Status | When you'll see it |
|---|---|---|
missing_authorization |
401 | No Authorization header |
invalid_token |
401 | Token doesn't match any active key |
revoked_token |
401 | Token was revoked |
plan_required |
403 | API isn't available on Free/Starter |
not_found |
404 | Resource doesn't exist, or isn't yours |
invalid_status |
400 | Bad ?status= value |
invalid_date_from |
400 | Bad ?date_from= format |
invalid_date_to |
400 | Bad ?date_to= format |
rate_limited |
429 | Over your per-minute quota |
Cross-account isolation¶
CredWatch is multi-tenant. Your API key only sees data belonging to your account.
If you request a resource ID that exists but belongs to another account, you'll get a 404 not_found — not a 403. We deliberately don't tell you "this exists but you can't see it" because that would leak the existence of other accounts.
Timeouts¶
- Server-side: requests are processed synchronously and should return in under 5 seconds for any list query. If you see consistent timeouts on small queries, check the status page.
- Client-side: set a 15–30 second timeout in your HTTP client.
Logging¶
Every API call hits our request log. If you're debugging a workflow and something is misbehaving, email [email protected] with:
- Your account slug
- The approximate time (UTC) of the failing request
- The HTTP status and
errorcode you got - What you were trying to do
…and we can pull the server-side log.