Errors and rate limits
The error envelope, the codes worth branching on, and what the limits are.
Every failure returns the same envelope: an error object carrying a stable machine-readable code, a human-readable message, and optional details. Branch on the code, never on the message — messages are copy and will be reworded.
{ "error": { "code": "channel.not_found", "message": "No such channel." } }Status codes
- 401 — no session, or the session has ended.
- 403 — authenticated, but the permission required is not held.
- 403 workspace.network_refused — the workspace allows access only from approved networks, and this request came from another. Call from one of its networks, or ask an owner to turn off Apply to apps too for your app.
- 404 — the resource does not exist, or you may not know that it does. Kemble masks existence rather than confirming it.
- 422 — the body failed validation. details names the field.
- 402 — a plan ceiling was reached. The message names the ceiling.
- 429 — too fast, or too many. The code says which limit; wait for the Retry-After header, then try again.
- 503 — the server is busy (server.busy) and is easing off, or file storage is busy for a moment (storage.busy). Wait for the Retry-After header, then send the same request again.
Plan limits are for apps, tokens and integrations
The plan’s rate limits apply to apps, API tokens, bots, incoming webhooks and the workflows they start, each per minute: requests per app or token, requests across an organization’s apps and tokens, and the messages an organization’s apps, webhooks and workflows may post. The numbers for each plan are on the pricing page, read from the same catalogue the server enforces. An app is metered per organization it is installed in, whichever of its tokens is used.
People using Kemble’s own apps are never metered by plan. Each person has one limit against abuse, across every session and device: 100 units a second, holding 300, refilling continuously. A read is one unit; a message or a change two; a search five; an upload, a K Buddy question or an export ten. Nothing a person does by hand comes near it.
What a limited answer says
Every metered answer carries RateLimit-Policy (the bucket, what it holds and the seconds it takes to refill) and RateLimit (what is left, and the seconds until it is full), with the x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset trio. A refusal adds Retry-After in whole seconds, never below one, and the milliseconds in details.retryAfterMs.
- rate_limit.exceeded — a person sent faster than their limit refills.
- rate_limit.repeated — the same request, again and again, in a few seconds. Fix the loop; retrying it is refused too.
- rate_limit.penalty — a cool-down: details.until says when it ends.
- rate_limit.app — this app or token has used its plan’s requests for the minute.
- rate_limit.organization — the organization’s apps and tokens have used their requests for the minute.
- rate_limit.messages — the organization’s apps, webhooks and workflows have posted their messages for the minute.
- rate_limit.webhook — this incoming webhook has posted sixty times this minute.
- server.busy — the server is easing off under load; heavy work goes first.
A caller that keeps sending after it is refused — thirty refusals in ten seconds — cools down: ten seconds, then thirty, two minutes and ten minutes for as long as it keeps going, with every request in between refused. Wait for Retry-After and it never happens.
An incoming webhook, and a workflow started by one, posts at most sixty times a minute, and every post counts toward its organization’s messages per minute, which its apps and workflows share. Past either, POST /api/hooks/:token answers 429 with a Retry-After header.
Where the space’s secrets detector is on, a webhook’s post, an app’s message and a workflow’s message are held to it as a person’s message is: one whose text or blocks look like an access key, token or private key is refused with 422 message.blocked_secret, and nothing is stored. A workflow records the step as failed, with the reason. K Buddy is held to it too: an answer that would carry a key is replaced by a line saying it was left out, and a routine that would post one records its run as failed and posts nothing.