Requests and errors
Base URL, authentication, response formats, streaming, extra per-IP limits, scan costs and the errors every Scanner API route can return.
This page covers what every route in the Scanner API reference shares. The route pages link back here instead of repeating it. Key setup, scopes, rotation and plan rules are in API keys.
Base URL and authentication
Every path in the reference is served under https://api.scanner.net. Every request carries a secret key in one header, either Authorization: Bearer sk_live_... or x-api-key: sk_live_.... Call the API from a server, never from a browser.
curl "https://api.scanner.net/explorer/address/WALLET_ADDRESS" \
-H "x-api-key: $SCANNER_API_KEY"Don't send a key query parameter. A secret key in the URL is refused with API_KEY_IN_URL, an emb_ key together with a secret key is refused with INVALID_INPUT, and any other key value is checked as an embed key and refused with 403 EMBED_KEY_INVALID.
A key is accepted by route group, not by exact path: it works under the groups this reference covers. Account and billing paths return 403 API_KEY_ROUTE_NOT_ALLOWED, and admin routes return 403. A path inside an allowed group that doesn't exist returns 404 NOT_FOUND. A route missing from this reference may still accept a key, but only the routes listed here are documented and supported.
Response formats
Routes return JSON unless the reference says the route streams. There are three JSON shapes, and each route page says which one it uses:
- Wrapped:
{ "success": true, "data": { ... } }. The explorer, holdings and wallet finder routes use it. - Flat with
success:{ "success": true, "wallets": [...], "count": 12 }. The KOL, alpha, identities, cross-chain and/v1routes use it. - Bare: the result object itself, with no
successfield. The scan, detect and bundle routes use it.
Timestamps are ISO 8601 strings unless a field says it is unix seconds or milliseconds. Addresses on Robinhood Chain come back lowercased.
Streaming routes
Routes whose path ends in /stream answer with server-sent events (Content-Type: text/event-stream). Each event is an event: line naming it and a data: line holding JSON:
event: transactions_progress
data: {"fetched":250}
event: complete
data: {"address":"...","fundedBy":[...],"scanComplete":true}Use curl -N (no buffering) or an SSE client. The final result arrives in the complete event, and the stream then closes.
- An error found before the stream opens (a bad address, too few scans) is a normal JSON error with an HTTP status.
- An error after the stream has opened arrives as an
errorevent with the usual error body,{ "error", "code", "requestId" }, and the stream closes. The HTTP status is already200by then. - Some streams send a
heartbeatevent every 15 seconds while work is running. Ignore it.
Errors
Errors use a standard body:
{ "error": "Invalid Solana address", "code": "INVALID_ADDRESS", "requestId": "..." }Some errors add fields, listed in the tables. A few routes answer some errors in a different shape. Their route pages say so, so check code only when it is present. Quote requestId when you contact support.
Errors any route can return
These come from key checks and limits that run before the route itself. The route pages list only their own errors.
| Status | Code | Meaning |
|---|---|---|
| 400 | API_KEY_IN_URL | The key was sent in a URL. Send it in a header, and roll the key. |
| 400 | INVALID_INPUT | The request carried more than one credential. |
| 401 | API_KEY_INVALID | The key doesn't exist. |
| 401 | API_KEY_REVOKED | The key was revoked, it expired, or its rotation grace period ended. |
| 403 | API_KEY_BROWSER_NOT_ALLOWED | The request came from a browser. |
| 403 | API_KEY_ROUTE_NOT_ALLOWED | The path is outside the route groups a key can call, such as account or billing. |
| 403 | API_KEY_IP_NOT_ALLOWED | The request IP isn't on the key's allowlist. |
| 403 | API_PLAN_REQUIRED | The plan doesn't include API access. The body includes upgradeUrl. |
| 403 | API_KEY_SCOPE_NOT_ALLOWED | The key is limited to other endpoint groups. The body lists them in allowedRouteGroups. |
| 403 | FORBIDDEN | API access is switched off for the account. |
| 403 | EMBED_KEY_INVALID | The request had a key query parameter. Remove it. |
| 429 | API_KEY_RATE_LIMITED | The key or account per-minute limit was reached. The body includes retryAfterSeconds and limit. |
| 429 | RATE_LIMITED | Too many invalid keys from your IP, or one of the per-IP limits below. |
| 500 | INTERNAL_ERROR | Something failed on our side. Retry, and contact support with the requestId if it keeps happening. |
| 503 | INTERNAL_ERROR | Key verification is briefly unavailable. Retry after a short delay. |
| 504 | TIMEOUT | The request ran past the server's time limit, or an upstream data provider timed out. |
Errors on paid routes
Routes that spend scans can also return these:
| Status | Code | Meaning |
|---|---|---|
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. Before the charge the body includes credits (your balance) and required. |
Robinhood Chain upstream errors
Robinhood Chain explorer and scan routes read from an upstream indexer. When it is down they return 502 UPSTREAM_ERROR. Retry after a short delay.
Rate limits
Every request counts toward the key's and the account's per-minute limit, described in Rate limits. Responses include X-RateLimit-Limit and X-RateLimit-Remaining.
Per-IP limits
A few route groups also have a limit per client IP address, on top of the key limit. These limits are counted per IP, not per key, so several services or customers behind one egress IP share them. When one is reached the route returns 429 RATE_LIMITED, with the standard RateLimit headers.
| Routes | Limit per IP |
|---|---|
/explorer/* | 60 requests per minute |
/rh/explorer/* | 60 requests per minute, counted apart from Solana |
/wallet-finder/search, /rh/wallet-finder/search | 30 requests per minute per chain |
POST /identities/batch | 60 requests per minute |
GET /holdings/evm/{address} | 20 requests per minute |
GET /scan/cached | The scan limit, 2,000 requests per 15 minutes by default |
Live origin traces (/explorer/address/{address}/origin and the Robinhood Chain equivalent) and the explorer's oldest-first transaction walk have a small extra budget per IP. See those routes.
Cost
Routes spend scans from the plan's allowance at the same price as the dashboard. Everything else is free, but still counts toward the rate limit.
| Route | Cost |
|---|---|
| Wallet scan, Solana or Robinhood Chain | 1 scan at depth 0 (Quick), 2 at depth 1 (Deep), 4 at depth 2 (Max) |
| Token scan, Solana or Robinhood Chain | 1 scan, free when a cached result is served |
| Multi-token scan | 1 scan per token without a cached result |
| Funding chain trace | 1 scan, free when a trace from the last 24 hours is cached |
| Cross-chain lookup | 1 scan for a live search, free for stored=1 and cached answers |
Cached scan reads, explorer, detection, identities, holdings, wallet finder, KOL, alpha, bundles, /v1 | Free |
Scans are charged before the work starts. A scan that fails is refunded automatically. Each route page gives the exact cache and refund rules.