Errors and limits
The error body
Every failure - whatever the status - answers the same JSON shape:
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Scanning is disabled for this site.",
"correlationId": "4f9c2a51-7d0e-4c3b-9a58-2b61f0d3e7aa",
"path": "/organizations/11111111-1111-4111-8111-111111111111/domains/22222222-2222-4222-8222-222222222222/scans",
"timestamp": "2026-09-24T10:00:00.000Z",
"details": { "reason": "scanning_disabled" }
}
| Field | What it is |
|---|---|
statusCode | the HTTP status, repeated |
error | the status's name |
message | a sentence for a person. Show it, log it - but do not branch on it: the wording may change |
correlationId | the id this request was logged under, also in the x-correlation-id header. Quote it to support |
path | the path that was requested |
timestamp | when the failure was answered |
details | present only on some 4xx answers: the machine-readable half of the refusal. Every member is optional |
Branch on details.reason, never on message. The members of details:
| Member | When |
|---|---|
reason | why the request was refused, from a closed list - the reasons an API key can receive are below |
limit, value | with organization_limit_exceeded: which of the organization's limits refused (for example maxScanRunsPerMonth) and what it is |
activeScanRunId | with the 409 of a scan start: the run of this site that is already queued or running |
fieldErrors | with a 400: messages per query parameter, keyed by the parameter's name |
state | with a report download's 409 or 410: pending, failed or unavailable; with an artifact's 410: expired |
expiredAt | with expired: when the artifact stopped being kept |
Status codes
| Status | Meaning for an API key | Retry? |
|---|---|---|
400 | a path id is not a UUID, or a query parameter is out of range (details.fieldErrors) | no - fix the request |
401 | the key is missing, malformed, unknown, revoked or expired, or was issued for another environment. One answer for all of them, on purpose | no - check the key |
403 | the key lacks the scope, or no key may make this request (details.reason below) | no - use a key with the right scope |
404 | nothing with this id - or it belongs to another organization, which answers exactly the same | no |
409 | a conflict with the current state: a scan already running, a report not ready yet (details.state pending) | after the state changes |
410 | gone and not coming back: a report that will never be rendered, an artifact past its retention period | no |
422 | understood but refused: scanning switched off, the site paused, an organization limit reached (details.reason below), or the site's scan policy names only modules that are not available yet (no details.reason) | no - not until the limit or setting allows |
429 | the key's per-minute limit (Rate limits) | yes - after Retry-After seconds |
5xx | any other failure - above all a 500, whose message is always "Internal server error" | yes - with backoff |
Every details.reason an API key can receive
The list of reasons is closed: a new one is an addition to the contract, announced in the changelog. These are the ones the operations an API key can call produce:
details.reason | Status | Meaning | What to do |
|---|---|---|---|
api_credential_scope_missing | 403 | the key does not hold the scope this operation names | create a key with that scope |
api_credential_not_allowed | 403 | no API key may make this request - an operation outside the list, or an artifact that is not a report PDF | use the dashboard for it |
api_credential_rate_limited | 429 | the key sent more requests this minute than its limit | wait Retry-After seconds |
scanning_disabled | 422 | scanning is switched off in this site's settings | switch it back on in the dashboard, or skip the site |
site_paused | 422 | the site is paused by Page Lookout, so no scan of it starts - by hand, with a key or on its schedule | skip the site; contact support to resume it |
organization_limit_exceeded | 422 | the organization reached one of its limits; details.limit and details.value say which and what | wait for the window to pass, or contact whoever manages your plan |
Treat a reason you do not recognise like the status it came with.
The details.reason list in the API reference is longer than this table: it also
holds reasons that only the dashboard's own routes answer - an organization at its limit of site
tags (site_tag_limit_reached) or of access groups (access_group_limit_reached), for example.
No request an API key can make is refused with them, and the changelog says so.
Likewise a separate 409 for a tag name that is already taken may carry details.existingTagId;
only the session routes answer it, so a key never receives it.
Site tags are read-only for a key: a site's tags come with the site list and the site read (at
most 500 per organization), and no request a key can make writes one.
A 422 without details.reason means the site's policy names no module this deployment runs;
change the policy in the dashboard.
Rate limits
Each API key may make 60 requests per minute, counted in fixed one-minute windows across all of the API. Every response to a key carries three headers:
| Header | Value |
|---|---|
RateLimit-Limit | the requests this key may make per window |
RateLimit-Remaining | how many are left in the current window |
RateLimit-Reset | seconds until the current window ends |
Past the limit the answer is 429 with details.reason api_credential_rate_limited and a
Retry-After header: wait that many seconds before the next request. The headers, not this
page, are the authority on your key's limit.
A few habits keep a client well inside it:
- poll a running scan every 15 to 30 seconds - or use the
scan.completedwebhook and do not poll at all; - ask for pages of 100 or 200 rather than 20 when you read a whole list;
- give each system its own key: each key has its own budget, and a key can then be replaced without touching the others.
Replacing a key
There is no "rotate" button, on purpose: a replacement is create, deploy, then revoke.
- Create a new key with the same scopes on the Integrations screen.
- Put it where the old one was, and check that requests with it succeed.
- Revoke the old key. Revocation is immediate: the next request with it answers
401.
An organization can hold 10 active keys, which leaves room for the overlap. A key that expires is refused the same way as a revoked one, so set a reminder before its expiry date - or replace it the same way, earlier.