Errors and rate limits
Every response outside the 2xx range uses the same envelope:
{ "error": { "code": "invalid_request", "message": "Invalid request", "requestId": "9f0c1c2e-…", "docs": "https://limitry.com/docs/errors/#invalid_request", "details": [{ "path": "name", "message": "Required" }] }}| Field | What it is |
|---|---|
code |
One of a small, stable set of codes. Branch on this. |
message |
A sentence for people. It may change, so don’t parse it. |
requestId |
Matches the X-Request-Id response header. Include it when you contact support. |
details |
Only on validation errors: one entry per field, with its path and message. |
docs |
A link to the code’s entry on this page. |
Error codes
Section titled “Error codes”We may add new codes over time. Treat a code you don’t recognise as
internal_error.
unauthorized
Section titled “unauthorized”401. The credential is missing, malformed, invalid, revoked or
expired. Send Authorization: Bearer <key or token> with a working
credential, and create a new one if it was revoked or has expired.
forbidden
Section titled “forbidden”403. The credential is valid but isn’t allowed to do this. Either it
doesn’t have the endpoint’s scope, or
your role in the workspace doesn’t allow it, or a personal access token
can’t act in the workspace named by X-Workspace. Use a credential with
the access it needs; the message names the missing scope.
Two more cases come from test and live. An API
key always acts in its own environment, so a request whose
X-Environment header names another one gets 403. A test key can read
workspace-wide settings but not change them, so adding a webhook
endpoint with a test key gets 403 with the message “A test key cannot
change workspace-wide settings”.
not_found
Section titled “not_found”404. The resource doesn’t exist, or isn’t yours to see. Check the path
and the id; the API reports an id from another workspace as not found.
method_not_allowed
Section titled “method_not_allowed”405. The path exists, but not with this method. Use one of the methods
in the Allow response header.
invalid_request
Section titled “invalid_request”400. The request didn’t pass validation. details lists each invalid
field with its path and message. An X-Environment header that names
an environment other than test or live gets this code too.
Two cases come from limits. A change to what a limit
counts (its subject, action, unit, window, every, start or
rolling) gets 400: create a new limit instead. A new limit, or a
rename, whose name an active limit already has gets 409 with this
code: send a name of its own.
rate_limited
Section titled “rate_limited”429. The credential has used its quota (see
API rate limits). Wait the number of seconds in
Retry-After, then retry with backoff.
plan_limit_reached
Section titled “plan_limit_reached”402. The workspace has used its plan’s monthly allowance for what this
request needs. The message says which allowance and when it resets, and
the workspace’s owners got an email and a notification at 80% and 100%. Upgrade the plan under
Settings → Billing, or wait for the month to reset; retrying sooner
gets the same answer.
Test checks have their own allowance and no grace. Past it, a check made
with a test key gets 402 with a message saying the workspace has used
its plan’s test checks for the month. Live checks are not affected.
idempotency_key_reused
Section titled “idempotency_key_reused”409. This Idempotency-Key was already used for a different request.
Use a new key for a new operation, and reuse a key only to retry the
same request (see Idempotency).
idempotency_in_progress
Section titled “idempotency_in_progress”409. A request with the same Idempotency-Key is still running. Wait
the number of seconds in Retry-After, then retry with the same key.
moving
Section titled “moving”503 with Retry-After: 1. Some counts are moving between Limitry’s
servers: a very busy workspace’s are spread over more servers as its
traffic grows, and a workspace’s move to a new region (Settings →
Region) carries them over. While some of them move, which takes a few
seconds, checks, commits and releases on those subjects answer
moving. Nothing was charged: anything held for
the request is given back, or finished by its retry. Retry with the same
Idempotency-Key after Retry-After; the SDKs do this for you for up
to 30 seconds before treating Limitry as unreachable.
503 with a Retry-After header (seconds). Limitry is deciding too
many checks for these subjects at once, so it answered this one at once
instead of making it wait in line. Nothing was charged: anything held
for the request is given back, or finished by its retry. Wait the number of
seconds in Retry-After, then retry with the same Idempotency-Key,
backing off with random jitter if it happens again. The SDKs do this
for you, within the same 30 seconds as moving, before treating
Limitry as unreachable. This is about Limitry’s load, not your quota:
it isn’t rate_limited, and it isn’t a refusal.
internal_error
Section titled “internal_error”500, or 503 with a Retry-After header when the failure was
momentary. Something failed on our side. A 503 saying the check “may
have been charged” means Limitry couldn’t confirm it in time: retry with
the same Idempotency-Key to get its answer, never without one. Retry with backoff (send an
Idempotency-Key and a retried check never counts twice), and if it
keeps happening, contact support with the requestId.
Refusals
Section titled “Refusals”A refused check isn’t an error. It answers 200 with
"allowed": false, and reason.code says why. The SDKs throw the
first two as LimitReached.
limit_reached
Section titled “limit_reached”A limit the check met is used up. reason.limitId names it, and
retryAfterSeconds says when it could be allowed (null for a balance,
which only credits raise). Your webhook endpoints also get
limit.reached the first time a
limit refuses a subject in a window.
too_many_reservations
Section titled “too_many_reservations”The subject already has 1,000 open reservations. Commit or release some, or wait for them to expire.
Unavailable
Section titled “Unavailable”Not a code from the API: the SDKs throw Unavailable when they get no
decision from Limitry (no answer in time, a 429, a 5xx, or a 402
plan_limit_reached) and a limit’s fail mode is closed. With open,
the work runs instead. See How Limitry works.
API rate limits
Section titled “API rate limits”Every authenticated request counts against a quota for its credential:
per API key on the workspace endpoints, per token on /v1/user/*. A
personal access token has a separate quota in test and in live. When
the quota runs out, the API answers:
HTTP/1.1 429 Too Many RequestsRetry-After: 60with code: "rate_limited". Wait for Retry-After, then retry with
backoff. Because limits are per credential, one busy integration doesn’t
slow down another key’s requests.