Token scans
Detect an address type, scan a token's holders or traders for bundles, compare several tokens, and read cached token scans, on Solana and Robinhood Chain.
A token scan looks at a token's holders (or, on Solana, its early traders), groups wallets that were funded together or bought together into bundles, and follows proxy wallets and profit collectors. It is the same scan as the token scan in the dashboard.
13 routes: 7 under /contracts for Solana and 6 under /rh/contracts for Robinhood Chain. Every path below is served under https://api.scanner.net, and every request carries the key header, so those two are not repeated per route. Shared errors, limits and streaming are in Requests and errors.
A token scan costs 1 scan. A cached result is free, and so is joining a scan of the same token that is already running. A scan that fails is refunded. The detect, history, cached and rebuild routes are free.
Solana token detect
GET /contracts/{address}/detectSays whether an address is a token, a program or a wallet. Free. For several addresses or either chain, use /detect.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
address | path | string | yes | Solana address. |
curl "https://api.scanner.net/contracts/MINT_ADDRESS/detect" \
-H "x-api-key: $SCANNER_API_KEY"Response: { isToken, isContract, isWallet } booleans, plus chain, family, address, exists, and owner, executable and balance when known. An address that can't be detected, including an invalid one, returns all three flags false.
| Status | Code | When |
|---|---|---|
| 502 | SCAN_FAILED | The lookup failed upstream. |
Solana token scan
POST /contracts/{mint}/scanScans a Solana token's holders and returns the result when it finishes. No body.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
mint | path | string | yes | Token mint address. |
force | query | string | no | true skips the cache and scans again. Read from the query string only. |
curl -X POST "https://api.scanner.net/contracts/MINT_ADDRESS/scan" \
-H "x-api-key: $SCANNER_API_KEY"Response: a ContractScanResult, with no wrapper. Key fields:
mintandtokenProfile: the token and its metadata.holders: the holders scanned, with their bundle and funding flags.bundleGroupsandtotalBundles: groups of wallets that act together.transferLinks,fundingSources,profitCollectors: how the holders are connected.proxyChainsandstaggeredScoring, when found.bundleSnapshotId: pass it to Rebuild bundles.scanComplete:falsewhen the result is partial.
Cost: a cached scan less than an hour old is returned free. Otherwise 1 scan.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_MINT | Not a valid Solana address. |
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. |
| 503 | INTERNAL_ERROR | Token data is temporarily unavailable. The scan is refunded. |
| 504 | TIMEOUT | The scan ran out of time. The scan is refunded. |
Solana token scan stream
GET /contracts/{mint}/scan/streamA Solana token scan as server-sent events. Unlike the JSON route, it can scan traders as well as holders.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
mint | path | string | yes | Token mint address. |
mode | query | string | no | holders (default) or traders. |
force | query | string | no | true skips the cache and scans again. |
curl -N "https://api.scanner.net/contracts/MINT_ADDRESS/scan/stream?mode=traders" \
-H "x-api-key: $SCANNER_API_KEY"Events: on a cache hit, cache_hit (scannedAt, isStale, history) and then complete. On a live scan, status, token_metadata, holders, entry_analysis, transfer_analysis, funding_analysis, staggered_analysis, proxy_detection, bundle_detection, bundle_groups, proxy_chains, staggered_scoring, heartbeat every 15 seconds, then complete with the ContractScanResult. A failure sends error with code TIMEOUT or INTERNAL_ERROR.
Cost: any cached scan is returned free, even one over an hour old (isStale: true). Pass force=true for a new scan at 1 scan. A failed scan is refunded, unless the connection was closed after results had already arrived.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_MINT | Not a valid Solana address. |
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. |
Solana token scan history
GET /contracts/{mint}/scan/historyUp to 3 recent cached scans of a token. Free.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
mint | path | string | yes | Token mint address. |
mode | query | string | no | holders (default) or traders. |
curl "https://api.scanner.net/contracts/MINT_ADDRESS/scan/history?mode=holders" \
-H "x-api-key: $SCANNER_API_KEY"Response: { "history": [{ "id", "scannedAt", "isStale" }] }. Pass an id to Cached token scan. An empty list means there is nothing cached, or the lookup failed; this route doesn't return errors.
Solana cached token scan
GET /contracts/scan/cache/{id}One cached token scan by its history id. Free.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
id | path | string | yes | An id from scan history. |
curl "https://api.scanner.net/contracts/scan/cache/CACHE_ID" \
-H "x-api-key: $SCANNER_API_KEY"Response: { scanResult, scannedAt, isStale }, where scanResult is a ContractScanResult.
| Status | When |
|---|---|
| 404 | No cached scan with this id. The body is { "error": "Cached scan not found" }, without a code. |
Solana rebuild bundles
POST /contracts/{mint}/bundles/rebuildRegroups the bundles of an earlier scan with some wallets hidden, for example a known exchange or a wallet you've already cleared. Free. It doesn't rescan.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
mint | path | string | yes | Token mint address. |
mode | body | string | yes | holders or traders, matching the scan. |
snapshotId | body | string | yes | bundleSnapshotId from the scan result. |
hidden | body | string[] | yes | Solana addresses to leave out, at most 100. Can be empty. |
curl -X POST "https://api.scanner.net/contracts/MINT_ADDRESS/bundles/rebuild" \
-H "x-api-key: $SCANNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mode":"holders","snapshotId":"SNAPSHOT_ID","hidden":["WALLET_ADDRESS"]}'Response: { "scanResult": { ... } }, the ContractScanResult with bundles regrouped.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_MINT | Not a valid Solana address. |
| 400 | INVALID_INPUT | Bad mode, snapshotId or hidden, or the snapshot belongs to another token or mode. |
| 410 | BUNDLE_SNAPSHOT_MISSING | The snapshot has expired. Run the scan again. |
| 503 | BUNDLE_REBUILD_UNAVAILABLE | Rebuilding is briefly unavailable. |
Multi-token scan
POST /contracts/multi/scan/streamScans 2 to 5 Solana tokens, holders and traders for each, and compares them: wallets they share, bundles that repeat, shared funding sources and profit collectors, and transfers between them. Answers with server-sent events.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
mints | body | string[] | yes | 2 to 5 different token mint addresses. |
force | body | boolean | no | true scans every token again instead of using cached results. |
curl -N -X POST "https://api.scanner.net/contracts/multi/scan/stream" \
-H "x-api-key: $SCANNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"mints":["MINT_ADDRESS_1","MINT_ADDRESS_2"]}'Events: preflight, multi_start (mints, creditsRequired), then per token target_start, mode_start, mode_cache_hit or mode_progress, mode_complete and target_complete, then cross_analysis. complete holds a MultiContractScanResult: mints, targets, commonWallets, repeatedBundles, sharedFundingSources, sharedProfitCollectors, crossTransfers and a summary with creditsRequired, creditsCharged and a verdict (high, medium, low or none).
Cost: 1 scan for each token that has no cached result, charged after the stream opens. Any failure refunds the whole charge.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_INPUT | Fewer than 2 or more than 5 tokens. |
| 400 | INVALID_MINT | An address isn't valid. |
After the stream opens, problems arrive as an error event: INVALID_MINT when an address isn't a token, INSUFFICIENT_CREDITS (with credits and required) when there aren't enough scans, and INTERNAL_ERROR. A lack of scans is reported this way, not as an HTTP 402.
Robinhood Chain token detect
GET /rh/contracts/{address}/detectSays whether a Robinhood Chain address is a token, a contract or a wallet. Free.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
address | path | string | yes | 0x address. |
curl "https://api.scanner.net/rh/contracts/0xTOKEN_ADDRESS/detect" \
-H "x-api-key: $SCANNER_API_KEY"Response: { isToken, isContract, isWallet }, plus contractName and supply when known.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_INPUT | Not a 0x address. |
| 502 | SCAN_FAILED | The lookup failed upstream. |
Robinhood Chain token scan
POST /rh/contracts/{address}/scanScans a Robinhood Chain token's holders and returns the result when it finishes.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
address | path | string | yes | Token 0x address. |
force | query or body | boolean | no | true (query force=true or body {"force":true}) skips the cache. |
curl -X POST "https://api.scanner.net/rh/contracts/0xTOKEN_ADDRESS/scan" \
-H "x-api-key: $SCANNER_API_KEY"Response: a RobinhoodContractScanResult, with no wrapper. Key fields: address, tokenProfile, holders, bundleGroups, totalBundles, transferLinks, fundingSources, profitCollectors, scanComplete, and degraded when some data was missing.
Cost: a cached scan is returned free unless force is true. Otherwise 1 scan.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_MINT | Not a 0x address. |
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. |
| 503 | INTERNAL_ERROR | Token data is temporarily unavailable. The scan is refunded. |
| 504 | TIMEOUT | The scan ran out of time. The scan is refunded. |
Robinhood Chain token scan stream
GET /rh/contracts/{address}/scan/streamA Robinhood Chain token scan as server-sent events. Holders only.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
address | path | string | yes | Token 0x address. |
force | query | string | no | true skips the cache. |
reuse | query | string | no | true also replays a cached result that was degraded or made by an older version of the scan. |
curl -N "https://api.scanner.net/rh/contracts/0xTOKEN_ADDRESS/scan/stream" \
-H "x-api-key: $SCANNER_API_KEY"Events: the same pattern as the Solana stream: cache_hit then complete for a cached result, otherwise progress events, heartbeat, and complete with the RobinhoodContractScanResult.
Cost: a complete cached scan is free, even when stale. Otherwise 1 scan, refunded on failure.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_MINT | Not a 0x address. |
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. |
Robinhood Chain token scan history
GET /rh/contracts/{address}/scan/historyRecent cached holder scans of a token. Free. The response has the same shape as the Solana history, and an empty list on any problem.
curl "https://api.scanner.net/rh/contracts/0xTOKEN_ADDRESS/scan/history" \
-H "x-api-key: $SCANNER_API_KEY"Robinhood Chain cached token scan
GET /rh/contracts/scan/cache/{id}One cached Robinhood Chain token scan by its history id. Free. Returns { scanResult, scannedAt, isStale }, or 404 without a code when there is no such scan.
curl "https://api.scanner.net/rh/contracts/scan/cache/CACHE_ID" \
-H "x-api-key: $SCANNER_API_KEY"Robinhood Chain multi-token scan
POST /rh/contracts/multi/scan/streamCompares 2 to 5 Robinhood Chain tokens, like the Solana multi-token scan. Answers with server-sent events.
| Parameter | In | Type | Required | Notes |
|---|---|---|---|---|
addresses | body | string[] | yes | 2 to 5 token 0x addresses. The field is addresses, not mints. |
force | body | boolean | no | true scans every token again. |
curl -N -X POST "https://api.scanner.net/rh/contracts/multi/scan/stream" \
-H "x-api-key: $SCANNER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"addresses":["0xTOKEN_1","0xTOKEN_2"]}'Events: multi_start, then per token target_start, target_cache_hit or target_progress, and target_complete, then cross_analysis, heartbeat, and complete with addresses, targets and a summary.
Cost: 1 scan for each token without a cached result, charged before the stream opens and refunded in full on failure.
| Status | Code | When |
|---|---|---|
| 400 | INVALID_INPUT | Fewer than 2 or more than 5 addresses. |
| 400 | INVALID_MINT | An address isn't valid. |
| 402 | INSUFFICIENT_CREDITS | Not enough scans left. Unlike Solana, this is an HTTP 402. |