# Wallet scans

Source: https://www.scanner.net/docs/api/reference/wallet-scans

> Run a wallet scan on Solana or Robinhood Chain as JSON or a live stream, or read the latest cached scan for free.

A wallet scan finds where a wallet's money came from, where it went, the wallets connected to it, and bundled wallets. It is the same scan as the [Deep Scan](/docs/scanner) in the dashboard.

6 routes. 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](/docs/api/reference/conventions).

Scan depth and cost [#scan-depth-and-cost]

`depth` sets how far the scan goes, and what it costs:

| `depth`       | Dashboard name | Cost    |
| ------------- | -------------- | ------- |
| `0`           | Quick          | 1 scan  |
| `1` (default) | Deep           | 2 scans |
| `2`           | Max            | 4 scans |

The scan is charged before it starts and refunded if it fails. The cached routes are free.

Solana wallet scan [#solana-wallet-scan]

```http
POST /scan
```

Runs a Solana wallet scan and returns the result when it finishes. A Max scan can take several minutes; use the [stream](#solana-wallet-scan-stream) to see progress.

| Parameter | In   | Type    | Required | Notes                                                            |
| --------- | ---- | ------- | -------- | ---------------------------------------------------------------- |
| `address` | body | string  | yes      | Solana address, or a `.sol` domain.                              |
| `depth`   | body | number  | no       | `0`, `1` or `2`. Default `1`.                                    |
| `fresh`   | body | boolean | no       | `true` skips any reused result and scans again. Default `false`. |

```bash
curl -X POST "https://api.scanner.net/scan" \
  -H "Authorization: Bearer $SCANNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address":"WALLET_ADDRESS","depth":1}'
```

**Response:** the scan result, a `WalletScanResult`, with no wrapper. Key fields:

* `address`: the scanned wallet.
* `originalFunder`: the first wallet or entity that funded it, when found.
* `fundedBy`, `fundingTo`, `fundingReceived`: wallets that sent funds to it and received funds from it.
* `connectedAddresses`: other wallets linked to it.
* `bundledWallets`: wallets that trade together with it.
* `walletBalances`: balances and USD value, when available.
* `solDomains`, `ownedSolDomains`: `.sol` domains linked to the wallet.
* `scanComplete`: `false` when the scan stopped early and the result is partial.

The cost is charged on every call, even when part of the result is reused.

| Status | Code                        | When                                                                                                                        |
| ------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| 400    | `MISSING_ADDRESS`           | No `address`.                                                                                                               |
| 400    | `INVALID_ADDRESS`           | Not a Solana address, or a `.sol` domain that doesn't resolve.                                                              |
| 400    | `INVALID_DEPTH`             | `depth` isn't 0, 1 or 2.                                                                                                    |
| 402    | `INSUFFICIENT_CREDITS`      | Not enough scans left.                                                                                                      |
| 403    | `NON_SCANNABLE_WALLET_SCAN` | The address is an exchange, protocol, token, program or liquidity pool, not a wallet. The body adds `entity` and `address`. |
| 504    | `TIMEOUT`                   | The scan ran out of time. The scan is refunded.                                                                             |

Solana wallet scan stream [#solana-wallet-scan-stream]

```http
GET /scan/stream
```

The same scan as [`POST /scan`](#solana-wallet-scan), sent as [server-sent events](/docs/api/reference/conventions#streaming-routes) while it runs. It costs the same.

| Parameter | In    | Type   | Required | Notes                               |
| --------- | ----- | ------ | -------- | ----------------------------------- |
| `address` | query | string | yes      | Solana address, or a `.sol` domain. |
| `depth`   | query | number | no       | `0`, `1` or `2`. Default `1`.       |
| `fresh`   | query | string | no       | `true` or `1` scans again.          |

```bash
curl -N "https://api.scanner.net/scan/stream?address=WALLET_ADDRESS&depth=2" \
  -H "x-api-key: $SCANNER_API_KEY"
```

**Events:** progress events such as `profile`, `transactions_progress` (`fetched`), `processing_complete`, `analysis_progress` (`step`, `status`), `original_funder`, `domains`, `identities`, `balances`, `bundle_detection` (`bundledCount`), `holdings`, `depth2_progress` and `kol_data`. Then `complete`, holding the full `WalletScanResult` described above. A failure sends `error` with code `TIMEOUT` or `INTERNAL_ERROR`, and the scan is refunded.

**Errors before the stream opens:** the same as [`POST /scan`](#solana-wallet-scan).

Solana cached wallet scan [#solana-cached-wallet-scan]

```http
GET /scan/cached
```

The most recent saved scan of a Solana wallet, from any Scanner user. Free. It never starts a scan.

| Parameter | In    | Type   | Required | Notes                                                                                                         |
| --------- | ----- | ------ | -------- | ------------------------------------------------------------------------------------------------------------- |
| `address` | query | string | yes      | Solana address. `.sol` domains are not accepted here.                                                         |
| `depth`   | query | number | no       | Preferred depth, `0`, `1` or `2`. If there is no saved scan at that depth, the deepest saved one is returned. |

```bash
curl "https://api.scanner.net/scan/cached?address=WALLET_ADDRESS&depth=1" \
  -H "Authorization: Bearer $SCANNER_API_KEY"
```

**Response:**

```json
{ "depth": 1, "scannedAt": "2026-11-12T09:30:00.000Z", "isStale": false, "scanResult": { "address": "...", "fundedBy": [] } }
```

`isStale` is `true` when the scan is more than an hour old. `scanResult` is a `WalletScanResult`. Check its `scanComplete` field: a saved scan can be partial.

This route has a [per-IP limit](/docs/api/reference/conventions#per-ip-limits).

| Status | Code                        | When                               |
| ------ | --------------------------- | ---------------------------------- |
| 400    | `INVALID_ADDRESS`           | Not a Solana address.              |
| 403    | `NON_SCANNABLE_WALLET_SCAN` | The address is not a wallet.       |
| 404    | `NOT_FOUND`                 | No saved scan for this wallet.     |
| 429    | `RATE_LIMITED_SCAN`         | The per-IP scan limit was reached. |

Robinhood Chain wallet scan [#robinhood-chain-wallet-scan]

```http
POST /rh/scan
```

Runs a Robinhood Chain wallet scan and returns the result when it finishes. Only externally owned wallets can be scanned, not contracts.

| Parameter | In   | Type    | Required | Notes                                   |
| --------- | ---- | ------- | -------- | --------------------------------------- |
| `address` | body | string  | yes      | `0x` address, 40 hex characters.        |
| `depth`   | body | number  | no       | `0`, `1` or `2`. Default `1`.           |
| `fresh`   | body | boolean | no       | `true` skips the cache and scans again. |

```bash
curl -X POST "https://api.scanner.net/rh/scan" \
  -H "x-api-key: $SCANNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"address":"0xWALLET_ADDRESS","depth":1}'
```

**Response:** a `RobinhoodWalletScanResult`, with no wrapper. Key fields: `address`, `originalFunder`, `fundedBy`, `fundingTo`, `fundingReceived`, `connectedAddresses`, `bundledWallets`, `walletBalances` (`ethBalance`, `ethUsdValue`, `totalUsdValue`, `topTokens`) and `scanComplete`.

**Cost:** a scan of the same wallet at the same depth from the last hour is returned free, unless `fresh` is `true`. Otherwise the [depth cost](#scan-depth-and-cost) applies.

| Status | Code                   | When                                                                |
| ------ | ---------------------- | ------------------------------------------------------------------- |
| 400    | `INVALID_INPUT`        | Invalid address, or `depth` isn't 0, 1 or 2.                        |
| 402    | `INSUFFICIENT_CREDITS` | Not enough scans left.                                              |
| 422    | `INVALID_INPUT`        | The address is a smart contract. Wallet scans only support wallets. |
| 502    | `UPSTREAM_ERROR`       | The Robinhood Chain indexer is unavailable. The scan is refunded.   |

Robinhood Chain wallet scan stream [#robinhood-chain-wallet-scan-stream]

```http
GET /rh/scan/stream
```

The same scan as [`POST /rh/scan`](#robinhood-chain-wallet-scan), as [server-sent events](/docs/api/reference/conventions#streaming-routes). It costs the same.

| Parameter | In    | Type   | Required | Notes                         |
| --------- | ----- | ------ | -------- | ----------------------------- |
| `address` | query | string | yes      | `0x` address.                 |
| `depth`   | query | number | no       | `0`, `1` or `2`. Default `1`. |
| `fresh`   | query | string | no       | `true` or `1` scans again.    |

```bash
curl -N "https://api.scanner.net/rh/scan/stream?address=0xWALLET_ADDRESS&depth=0" \
  -H "x-api-key: $SCANNER_API_KEY"
```

**Events:** on a cache hit, `cache_hit` (`scannedAt`, `isStale`) and then `complete`. On a live scan, progress events (`profile`, `transactions_progress`, `processing_complete`, `analysis_progress`, `original_funder`, `balances`, `bundle_detection`, `holdings`), `heartbeat` every 15 seconds, then `complete` with the `RobinhoodWalletScanResult`. A failure sends `error` with code `INVALID_INPUT` (contract address), `UPSTREAM_ERROR`, `TIMEOUT` or `INTERNAL_ERROR`.

**Errors before the stream opens:** the same as [`POST /rh/scan`](#robinhood-chain-wallet-scan).

Robinhood Chain cached wallet scan [#robinhood-chain-cached-wallet-scan]

```http
GET /rh/scan/cached
```

The most recent saved scan of a Robinhood Chain wallet. Free.

| Parameter | In    | Type   | Required | Notes                             |
| --------- | ----- | ------ | -------- | --------------------------------- |
| `address` | query | string | yes      | `0x` address.                     |
| `depth`   | query | number | no       | Preferred depth, `0`, `1` or `2`. |

```bash
curl "https://api.scanner.net/rh/scan/cached?address=0xWALLET_ADDRESS" \
  -H "x-api-key: $SCANNER_API_KEY"
```

**Response:** `{ depth, scannedAt, isStale, scanResult }`, where `scanResult` is a `RobinhoodWalletScanResult`.

| Status | Code            | When                           |
| ------ | --------------- | ------------------------------ |
| 400    | `INVALID_INPUT` | Invalid address.               |
| 404    | `NOT_FOUND`     | No saved scan for this wallet. |
