Skip to content

Rate limits & errors

Limits are per route, over a rolling 60-second window, and are counted per client IP address rather than per key. Several keys behind one egress address share a bucket.

Route Limit per 60s
GET /api/v1/opportunities 60
GET /api/v1/opportunities/{id} 60
GET /api/v1/me 60
POST /api/v1/opportunities/{id}/archive 10
DELETE /api/v1/opportunities/{id}/archive 10
POST /api/v1/opportunities/{id}/feedback 10
GET /api/v1/api-keys 10
POST /api/v1/api-keys 10
DELETE /api/v1/api-keys/{id} 10
POST /api/v1/session 10

GET /api/v1/scopes, GET /api/v1/openapi.json, and DELETE /api/v1/session carry no route limit.

Throttled routes report their budget on every response:

Header Meaning
x-ratelimit-limit The route’s maximum for the window.
x-ratelimit-remaining Requests left in the current window.
x-ratelimit-reset Seconds until the window resets.
retry-after Seconds to wait. Sent on a 429 only.

The request is rejected with 429 and this body:

{
"error": "rate_limited",
"message": "too many requests",
"requestId": "req-b"
}

Honour retry-after when it is present, and fall back to exponential backoff with jitter when it is not. Do not retry immediately in a tight loop — the window is rolling, so a burst of retries keeps the bucket empty and extends the lockout.

Every error from the API — validation, auth, rate limiting, and internal failures alike — serializes to one shape:

{
"error": "insufficient_scope",
"message": "this key lacks the opportunities:read scope",
"requestId": "req-4f2"
}
Field Type Notes
error string A stable code from the table below. Branch on this, never on message.
message string Human-readable detail. Wording may change; treat it as diagnostic text.
requestId string Identifies this specific request.
issues array Optional. Present on some validation failures, listing the specific problems.
Code Typical status Meaning
unauthorized 401 No credential presented, or the key is unknown, revoked, or expired.
forbidden 403 The credential is valid but not permitted to perform this action.
insufficient_scope 403 The key is missing a scope the route requires.
no_membership 403 The account is not linked to a company.
membership_disabled 403 The membership behind this credential has been disabled.
unverified_email 403 The signed-in account has not confirmed its email address.
identity_conflict 403 The membership is bound to a different identity.
validation_failed 400 The request was malformed — a bad parameter, body, or cursor.
not_found 404 No such resource for this company.
rate_limited 429 The route’s limit for the window was exceeded.
internal 500 Something failed on our side. Safe to retry with backoff.

unauthorized, insufficient_scope, validation_failed, and not_found are the four an API-key integration will realistically encounter. The membership-related codes surface on browser sessions, not on key auth.

  • 429 and 5xx — transient. Retry with backoff.
  • 401 and 403 — the credential itself is wrong. Retrying will not fix it; check the key and its scopes.
  • 400 and 404 — the request is wrong. Retrying sends the same wrong request.