Your first error
The handful of responses you hit on a first Starkscan call — what each means and the exact fix.
Your first error
Almost every first-call problem is one of a few responses. Each has a clear fix. Get a key first with Get your first API key.
Quick triage
| Status | Meaning | Fix |
|---|---|---|
401 | Key missing or invalid | Send X-Starkscan-Api-Key: <your key> — no Bearer prefix. See Authentication. |
403 | Valid key, but the route or tier is not allowed | Use a route in your tier; batch / advanced-utility routes need a broader tier — see Advanced utilities. |
404 | Route or resource not found | Check the path shape https://api.starkscan.co/v1/{chain}/... (Base URLs and chains) and that the id exists. |
410 | Requested Sepolia history is before Starkscan's fixed indexed-history starting block | Read the typed boundary fields from the response or /status, then restart the range or cursor traversal at earliestAvailableBlock. Current state and preserved lifecycle facts remain available. |
422 | A token-holder route has no current explicit address-keyed policy (unqualified_token_policy) | Terminal: do not retry, treat it as zero holders, or fall back to client-side scanning. See Token holders. |
429 | Rate limit reached for the route class | Honor Retry-After; back off per X-Starkscan-Route-Class — see Rate limits. |
400 | API key sent under more than one header (code conflicting_api_key_headers) | Send it once, as X-Starkscan-Api-Key. |
400 | Malformed API key header value (code malformed_api_key_header) | Remove stray quotes or whitespace. |
The error envelope
{
"code": "rate_limited",
"message": "Rate limit exceeded; retry shortly",
"docSlug": "api/rate-limits",
"requestId": "mzk-..."
}Log requestId (also returned as X-Request-Id) whenever you report an issue.
Failure response examples
Every failure uses the same envelope with a stable code. The ones you hit first (the message text is illustrative — key off code, not the exact wording):
400 — key sent under more than one header
HTTP/1.1 400 Bad Request{
"code": "conflicting_api_key_headers",
"message": "API key supplied under multiple headers; send it once as X-Starkscan-Api-Key",
"requestId": "mzk-..."
}403 — valid key, route or tier not allowed
HTTP/1.1 403 Forbidden{
"code": "forbidden",
"message": "Key is valid but lacks the required route tier or scope",
"requestId": "mzk-..."
}422 — token-holder policy review is required
HTTP/1.1 422 Unprocessable Entity{
"code": "unqualified_token_policy",
"message": "token holder enumeration requires an explicit address-keyed policy review",
"docSlug": "api/errors",
"requestId": "mzk-..."
}The route-specific evidence matrix matters. /holders returns this error only
when positive indexed fungible evidence exists without a producer policy;
unknown or terminal non-ERC20 identities return 404. Screening and analytics
gate directly on policy membership, so any unregistered identity returns this
422 regardless of indexed discovery evidence. It is terminal and has no
Retry-After: do not
retry it, treat it as a zero-holder answer, or make a request-time
provider/RPC scan. It is distinct from a token's non-exact 200 holder rows
and from an eligible token's temporary retryable 503.
429 — rate limit reached
HTTP/1.1 429 Too Many Requests
Retry-After: 2
X-Starkscan-Route-Class: heavy{
"code": "rate_limited",
"message": "Rate limit exceeded; retry shortly",
"docSlug": "api/rate-limits",
"requestId": "mzk-..."
}Honor Retry-After (seconds) and keep backoff state per X-Starkscan-Route-Class, so a heavy limit does not stall cheap light reads.
Walkthroughs
401 — verify the header
The usual cause is a missing/typo'd header or a Bearer prefix (there is none). Use -i to inspect the response:
curl -i -H "X-Starkscan-Api-Key: $STARKSCAN_API_KEY" \
"${STARKSCAN_BASE_URL:-https://api.starkscan.co}/v1/$STARKSCAN_CHAIN/status"A 200 with chain status means auth is wired correctly.
404 — check the base path
Usually a wrong base path. The canonical API-host rule lives in Base URLs and chains — confirm hosted calls use https://api.starkscan.co/v1/..., or app-host compatibility uses https://starkscan.co/api/v1/....
410 — advance to the Sepolia history floor
Sepolia has a fixed indexed-history starting block. An exact older block or
transaction, an explicit block range that crosses the boundary, or a cursor
asking for the next older page returns 410 with code="history_expired",
historyPolicy="fixed_start", historyCompleteness, earliestAvailableBlock,
and earliestAvailableAt. Do not retry the same request. Advance the lower
bound to earliestAvailableBlock or begin a new cursor traversal. Mainnet is
not governed by this Sepolia history boundary.
403 — wrong tier
The route is valid but not in your key's tier (often batch or advanced-utility routes). See Advanced utilities.
422 — choose an eligible token-holder workflow
For /holders, unqualified_token_policy means positive indexed fungible
evidence exists but the token lacks a current explicit address-keyed producer
policy; unknown or terminal non-ERC20 identities return 404. Screening and
analytics test policy membership directly, so every unregistered identity
returns 422 regardless of indexed discovery evidence. The response is
terminal and has no Retry-After, is not an empty census, and is not a request
to retry. It is also distinct from a token's non-exact 200 holder rows and an
eligible token's temporary 503. See Token holders.
429 — back off by route class
Honor Retry-After and keep backoff state per X-Starkscan-Route-Class, so a heavy limit doesn't stop cheap light reads. See Rate limits.
Still stuck?
When you ask for help, include the host, the exact route, X-Request-Id, X-Starkscan-Route-Class, the status code, and a short response snippet. Never share your API key or any auth headers — redact them before sending logs.