Skip to content

Errors & rate limits

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 on message.
  • message — human-readable, may change between releases, sometimes safe to show verbatim to end users.
  • Some errors include a details array 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.

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 — the knak_ token is wrong, expired, or revoked.

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 Requests
Retry-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.

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.

  • Safe to retry: 429 (after backoff), 500, network timeouts on GET.
  • Retry with idempotency: mutating calls that accept clientMutationId (or Idempotency-Key on 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.