Errors & rate limits
The error envelope
Section titled “The error envelope”Every non-2xx response carries one JSON envelope:
{ "error": { "code": "NOT_FOUND", "message": "thread not found" }}code— a stable machine-readable enum. Branch on this, never onmessage.message— human-readable, may change between releases, sometimes safe to show verbatim to end users.- Some errors include a
detailsarray with structured extras.
The HTTP status always mirrors the code. Endpoints may return more specific lowercase sub-codes (for example invalid_title, subdomain_taken, hydrating) — treat any code you don’t recognize according to its HTTP status.
Canonical codes
Section titled “Canonical codes”| Code | HTTP | Meaning | What to do |
|---|---|---|---|
INVALID_ARGUMENT |
400 | malformed body, bad field value, unsupported format | fix the request; don’t retry as-is |
UNAUTHENTICATED |
401 | missing, expired, or invalid bearer | re-exchange the session / check the API key |
PERMISSION_DENIED |
403 | authenticated, but your role or ACL doesn’t allow it | not retryable |
ONBOARDING_REQUIRED |
403 | the session is still mid-onboarding | finish onboarding in the app, re-exchange |
NOT_FOUND |
404 | doesn’t exist — or exists but is hidden from you | Knobs hides existence: a resource you can’t see is indistinguishable from one that isn’t there |
FAILED_PRECONDITION |
409 | right request, wrong state (e.g. domain not verified, attachment not yet extracted) | resolve the precondition, retry |
ABORTED |
409 | optimistic-concurrency conflict (baseRevision stale) |
refetch, reapply, retry |
RESOURCE_EXHAUSTED |
429 | rate limit or quota exceeded | back off; honor Retry-After |
INTERNAL |
500 | server fault | retry with exponential backoff |
Two auth-specific 401 sub-codes worth handling explicitly:
api_key_not_allowed— you called a session-only (credential-management) route with an API key. See API keys.invalid_api_key— theknak_token is wrong, expired, or revoked.
Rate limiting (429)
Section titled “Rate limiting (429)”Rate limits are token buckets applied per workspace and per user; some surfaces add their own (AI generation, invite sending, anonymous auth per IP, failed API-key attempts per IP).
A limited response looks like:
HTTP/1.1 429 Too Many RequestsRetry-After: 12{ "error": { "code": "RESOURCE_EXHAUSTED", "message": "too many AI requests; try again in a minute" } }Handle it by waiting at least Retry-After seconds (when present) before retrying, with jittered exponential backoff as a fallback.
Bot API limits
Section titled “Bot API limits”The integration bot API additionally returns X-RateLimit-Remaining and X-RateLimit-Reset headers on every response (60 requests/minute across the bot API; 1 message/second/channel on posting). See Integrations & webhooks.
Retry guidance
Section titled “Retry guidance”- Safe to retry: 429 (after backoff), 500, network timeouts on
GET. - Retry with idempotency: mutating calls that accept
clientMutationId(orIdempotency-Keyon the bot API) can be retried safely with the same value — replays dedupe server-side. - Don’t retry unchanged: 400, 401, 403, 404, and 409
FAILED_PRECONDITION— the request or state must change first.