On-chain data API docs
Blockchain data API reference: every /v1/onchain route for Ethereum, BNB Chain, Base and HyperEVM — wallets, transactions, labels and tokens — with its parameters, its limits and a request to copy in cURL, Python, the CLI or MCP. Base URL https://data.vike.io/v1, one header, 21 routes.
“Label these 50 addresses and tell me which ones are exchanges.”
wallet_batch_labels“What happened in this transaction, and who are the two sides? (paste a hash)”
transaction_lookupThree steps to the first request
- Sign in at vike.io
Any account. No card, no plan to pick.
- Create a key
On
/account?tab=api-keys. Up to 3 active keys per account; every key startsvk_. - Send the header
X-API-KEY: vk_…on every request. Or skip the code:vike-data initin the CLI, or connect MCP.
Look up a wallet
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet summary 0xd8da6bf26964af9d7eed9e03e53415d37aa96045
# any MCP client; or from the terminal:
vike-data call wallet_summary --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"}'
- One call: the wallet's volume, inflow, outflow, net flow and transfer count over 24 hours, 7, 30 and 90 days and all time.
- Addresses are
0x+ 40 hex digits in any case; the answers spell them in lower case. chainis a filter, not a path:ethereum,bsc,baseorhyperevm.- Every route is also an MCP tool, named on its card below.
Free, with one ceiling per key
| Requests | 20,000 per hour, per key, a rolling hour |
|---|---|
| Per minute | 200 per minute, per key, on /v1 |
| Keys | Up to 3 active per account; each key is its own budget |
| Cost of a call | 1 per REST request; MCP tools 0 to 5 credits |
| Price | $0, nothing is billed |
Request builder: 20 GET routes
Pick a route; the parameters come filled with real addresses. The call is rebuilt as you type, and a captured route shows its real answer.
All 21 routes
Click a route to jump to its parameters and a call you can copy.
Wallet routes
What a wallet holds and made, who is behind it, who it deals with, and the wallets worth following.
Wallet Summary
- Route
GET /v1/onchain/wallets/{address}- CLI
vike-data wallet summary- MCP
wallet_summary· 2 credits- Cost
- 1 request of the hourly limit
Aggregate metrics for one wallet — volume, inflow, outflow, net flow, transaction count and tokens traded, across the 24h / 7d / 30d / 90d / all windows in a single response. tokens_traded counts the distinct tokens moved within each window. chain in the response says which chain answered.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed hex |
chain | query | ethereum | bsc | base | hyperevm. Omit for the more recently active of ethereum and bsc; base or hyperevm is then answered only for a wallet with no priced ethereum/bsc volume |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet summary 0xd8da6bf26964af9d7eed9e03e53415d37aa96045
# any MCP client; or from the terminal:
vike-data call wallet_summary --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"}'
Wallet Positions
- Route
GET /v1/onchain/wallets/{address}/positions- CLI
- —
- MCP
wallet_positions· 2 credits- Cost
- 1 request of the hourly limit
A wallet's per-token positions with real profit and loss. Per token: bought and sold quantity and USD, average buy and sell price, realized PnL, the balance read from the chain now, the latest price, the current value and unrealized PnL (balance × (price − average buy price), null when either is unknown). Realized PnL is average cost over the wallet's priced inbound and outbound transfers: only units that were bought are realized, and the chain's coin and its wrapped token are one position under the zero address. totals sum every token the wallet traded; as_of is when its lifetime PnL in the wallet rankings was last recomputed (updated nightly at 04:00 UTC). For a daily net-flow series see /wallets/{address}/pnl.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed hex |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
sort | query | realized | unrealized | value. Default realized. Positions whose value is unknown sort last |
order | query | desc (biggest first) | asc (biggest losses first). Default desc |
limit | query | Positions returned. Default 20, max 100. totals always cover every token |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/positions?sort=value&limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/positions?sort=value&limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
# any MCP client; or from the terminal:
vike-data call wallet_positions --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "sort": "value", "limit": 10}'
Wallet Token Balances
- Route
GET /v1/onchain/wallets/{address}/balances- CLI
vike-data wallet balances- MCP
wallet_token_balances· 3 credits- Cost
- 1 request of the hourly limit
Current token balances for a wallet on Ethereum, BSC, Base and HyperEVM, read from the chain at request time, with a USD value where the token is priced. On base and hyperevm only the tokens we map and price carry a symbol, balance and USD value; any other held token is listed by address and balance_raw only with min_usd=0. HyperCore (spot and perp accounts) is not included.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed, exactly 42 characters |
chains | query | Comma-separated. ethereum, bsc, base and hyperevm; the aliases eth, mainnet, bnb and binance are accepted. Omit for all four. Any other chain is reported in chain_errors; a chain whose read failed is listed there too and not in chains_scanned. |
min_usd | query | Hide holdings worth less than this. Omitted resolves to 1 (USD); an explicit 0 filters nothing and lists every holding. ⚠ Any floor above 0 also excludes holdings we cannot price, since an unpriced holding cannot be shown to clear it. The chain's native coin is the exception: it is always listed (when its balance is not zero), priced where a price exists, whatever the floor. |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/balances?min_usd=1000"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/balances?min_usd=1000",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet balances 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --min-usd 1000
# any MCP client; or from the terminal:
vike-data call wallet_token_balances --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "min_usd": 1000}'
Wallet PnL History
- Route
GET /v1/onchain/wallets/{address}/pnl- CLI
vike-data wallet pnl-history- MCP
wallet_pnl_history· 2 credits- Cost
- 1 request of the hourly limit
A daily net-flow series for the wallet — per-day net inflow USD, a cumulative figure, gross volume and transaction count.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
days | query | Days back. Default 30, clamped to the range 1–365 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/pnl?days=90"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/pnl?days=90",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet pnl-history 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --days 90
# any MCP client; or from the terminal:
vike-data call wallet_pnl_history --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "days": 90}'
Wallet Discover
- Route
GET /v1/onchain/wallets/discover- CLI
vike-data wallet discover- MCP
wallet_discover· 2 credits- Cost
- 1 request of the hourly limit
Leaderboard of active wallets. Every USD value is in plain dollars. realized_pnl and tokens_profitable are lifetime figures in every window (realized_pnl_basis: "lifetime"), so sort=pnl ranks by lifetime PnL whatever window is.
Parameters
| Parameter | In | Description |
|---|---|---|
window | query | 24h | 7d | 30d | 90d | all. Default 30d; base and hyperevm default to all and serve the other windows only while those windows were refreshed in the last 2 days, otherwise a 400 |
sort | query | volume | inflow | outflow | net_flow | tx_count | pnl. Default volume |
order | query | asc | desc. Default desc |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
size | query | Results per page. Default 10, max 20 — one of the two routes in this namespace whose accepted and applied caps agree, alongside /tokens/search |
include_hubs | query | Boolean, default false. Also rank round-trip hubs (flash-loan, MEV and router pass-through wallets: volume of $10B or more, with net flow and realized PnL each within 0.1% of it), which are hidden by default |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/discover?window=7d&sort=net_flow&size=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/discover?window=7d&sort=net_flow&size=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet discover --window 7d --sort net_flow --size 10
# any MCP client; or from the terminal:
vike-data call wallet_discover --args '{"window": "7d", "sort": "net_flow", "size": 10}'
Wallet ENS
- Route
GET /v1/onchain/wallets/{address}/ens- CLI
vike-data wallet ens- MCP
wallet_ens· 1 credit- Cost
- 1 request of the hourly limit
Reverse ENS lookup — turn an address into vitalik.eth. Returns null where the address has no ENS name set.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed, exactly 42 characters |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/ens"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/ens",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet ens 0xd8da6bf26964af9d7eed9e03e53415d37aa96045
# any MCP client; or from the terminal:
vike-data call wallet_ens --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"}'
Wallet Check
- Route
GET /v1/onchain/wallets/{address}/check- CLI
- —
- MCP
wallet_check· 5 credits- Cost
- 1 request of the hourly limit
Everything we know about one address in one call: its ENS name, labels per chain, which source claimed what (Ethereum) and where each other chain's label came from (label_origins: third_party, model, cross_chain or unknown), transfer counts per chain, the code found at the address, token details when it is a token contract, the earliest inbound transfers and the centralised exchanges it used on each chain it is active on, and its Hyperliquid and Polymarket identity. summary lists plain facts read off those blocks, never a verdict such as "safe". A block missing from the answer could not be read; an empty block means we looked and found nothing. first_funder is not proof of who funded the wallet, and exchange figures are a lower bound (the coverage of each says why).
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet or contract address, 0x-prefixed hex, 42 characters |
chain | query | ethereum | bsc | base | hyperevm. Reads first_funder and exchanges on this chain only. Default: every chain the address has transfers on |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0x28c6c06298d514db089934071355e5743bf21d60/check?chain=ethereum"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0x28c6c06298d514db089934071355e5743bf21d60/check?chain=ethereum",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
# any MCP client; or from the terminal:
vike-data call wallet_check --args '{"address": "0x28c6c06298d514db089934071355e5743bf21d60", "chain": "ethereum"}'
Wallet First Funder
- Route
GET /v1/onchain/wallets/{address}/first-funder- CLI
vike-data wallet first-funder- MCP
wallet_first_funder· 2 credits- Cost
- 1 request of the hourly limit
The earliest inbound transfers of a wallet, as two separate answers: first_native_in (the first plain send of the chain's coin) and first_token_in (the first priced token transfer), each with the sender, its label, time, transaction hash, block and amounts. Not proof of who funded the wallet: native rows hold top-level sends only, and history starts on a different date per chain (coverage).
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed hex |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum. Read from that chain's own transfer and label tables |
min_native | query | Dust floor for the native coin, in whole coins (ETH, BNB or HYPE; 0.01 is 0.01 coin, not wei). Inbound native transfers below it are skipped. Omitted resolves to 0.0001; an explicit 0 means no floor and returns the very first inbound, probing dust included |
min_usd | query | Floor for the token side, in USD. Token transfers priced below it, or with no USD price, are skipped. Omitted resolves to 1; an explicit 0 means no floor and includes unpriced tokens (mostly spam) |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/first-funder"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/first-funder",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet first-funder 0xd8da6bf26964af9d7eed9e03e53415d37aa96045
# any MCP client; or from the terminal:
vike-data call wallet_first_funder --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"}'
Wallet Exchange Usage
- Route
GET /v1/onchain/wallets/{address}/exchange-usage- CLI
vike-data wallet exchange-usage- MCP
wallet_exchange_usage· 3 credits- Cost
- 1 request of the hourly limit
Which centralised exchanges a wallet sent to and received from over a window: USD sent and received, transfer counts and first and last contact per exchange, plus pattern: "sweeps_to_single_cex" when at least 90% of the wallet's outgoing USD went to one exchange across at least 3 transfers. The pattern is observed, not a claim that the address is a deposit address, and exchange figures are a lower bound.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed hex |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum. hyperevm has few labels, so a miss there says little |
window | query | 30d | 90d | 180d | 365d | all. Default 365d. A very active wallet over a long window is a 400 naming this parameter: narrow it |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/exchange-usage?window=90d"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/exchange-usage?window=90d",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet exchange-usage 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --window 90d
# any MCP client; or from the terminal:
vike-data call wallet_exchange_usage --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "window": "90d"}'
Wallet Counterparties
- Route
GET /v1/onchain/wallets/{address}/counterparties- CLI
vike-data wallet counterparties- MCP
wallet_counterparties· 2 credits- Cost
- 1 request of the hourly limit
The addresses this wallet transacted with most, by combined transfer volume over a window.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
window | query | 24h | 7d | 30d | 90d | 180d | all. Default 30d |
limit | query | Route default 100; applied cap 50 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/counterparties?window=90d&limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xd8da6bf26964af9d7eed9e03e53415d37aa96045/counterparties?window=90d&limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet counterparties 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 --window 90d --limit 10
# any MCP client; or from the terminal:
vike-data call wallet_counterparties --args '{"address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "window": "90d", "limit": 10}'
Wallet Compare
- Route
GET /v1/onchain/wallets/compare- CLI
vike-data wallet compare- MCP
wallet_compare· 3 credits- Cost
- 1 request of the hourly limit
Are these two wallets related? Returns the counterparties both wallets transacted with and the tokens both touched, within a window.
Parameters
| Parameter | In | Description |
|---|---|---|
address_arequired | query | First wallet |
address_brequired | query | Second wallet |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
window | query | 24h | 7d | 30d | 90d | 180d | all. Default 30d |
limit | query | Max shared items per category. Route default 100; applied cap 50 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/compare?address_a=0x28c6c06298d514db089934071355e5743bf21d60&address_b=0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4&window=30d&limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/compare?address_a=0x28c6c06298d514db089934071355e5743bf21d60&address_b=0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4&window=30d&limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet compare 0x28c6c06298d514db089934071355e5743bf21d60 0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4 --window 30d --limit 10
# any MCP client; or from the terminal:
vike-data call wallet_compare --args '{"address_a": "0x28c6c06298d514db089934071355e5743bf21d60", "address_b": "0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4", "window": "30d", "limit": 10}'
Transactions and the money trail
A wallet's transfers, one transaction by its hash, and the money followed hop by hop.
Wallet Transactions
- Route
GET /v1/onchain/wallets/{address}/transactions- CLI
vike-data wallet transactions- MCP
wallet_transactions· 2 credits- Cost
- 1 request of the hourly limit
The raw transfer list for a wallet: individual transfers, not aggregates. The forensic route. Each row carries the token's symbol and amount in whole tokens (the amount min_amount and max_amount filter on; null when the token's decimals are unknown) beside its USD value.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
direction | query | in | out. Omit for both directions |
days | query | Days back. Default 30, clamped to 365 |
min_usd | query | Drop transfers below this USD value. Default 0 |
token_address | query | Restrict to a single token contract |
min_amount | query | Only transfers of at least this many tokens, in whole tokens (not raw units), up to 1e18. Needs token_address: an amount across different tokens means nothing, so without it this is a 400, as it is when that token's decimals are unknown. With min_usd, both must hold |
max_amount | query | Only transfers of at most this many tokens, in whole tokens, up to 1e18. Same rules as min_amount; a max_amount below min_amount is a 400 |
limit | query | Route default 100; applied cap 500 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4/transactions?days=30&token_address=0xdac17f958d2ee523a2206206994597c13d831ec7&limit=50"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4/transactions?days=30&token_address=0xdac17f958d2ee523a2206206994597c13d831ec7&limit=50",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet transactions 0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4 --token 0xdac17f958d2ee523a2206206994597c13d831ec7 --days 30 --limit 50
# any MCP client; or from the terminal:
vike-data call wallet_transactions --args '{"address": "0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4", "token_address": "0xdac17f958d2ee523a2206206994597c13d831ec7", "days": 30, "limit": 50}'
Transaction Lookup
- Route
GET /v1/onchain/transactions/{tx_hash}- CLI
- —
- MCP
transaction_lookup· 2 credits- Cost
- 1 request of the hourly limit
What happened in one transaction: status (success, failed, pending; unknown for Ethereum transactions before October 2017), block, time, from and to with their labels, the coin sent at the top level (value: whole coins, wei and USD), gas used and the fee, and every ERC-20 transfer in it (token_transfers: token, symbol, decimals, from and to with labels, amount in whole tokens and raw, USD). Each USD value carries usd_basis: transfer_time (the price at the transaction's minute) or latest_daily_price (only for a transaction from the last 24 hours not priced yet); null means no price. NFT transfers and internal transfers made by contract calls are not listed. found: false when the hash does not exist on that chain.
Parameters
| Parameter | In | Description |
|---|---|---|
tx_hashrequired | path | Transaction hash: 0x followed by 64 hex characters. Anything else is a 400 |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum. A hash looks the same on every chain: pass the one it was sent on |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/transactions/0x0e4fdf7d3742366a474d7fc6baa538e4cad839eaa95f2c798403bd47deb795db"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/transactions/0x0e4fdf7d3742366a474d7fc6baa538e4cad839eaa95f2c798403bd47deb795db",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
# any MCP client; or from the terminal:
vike-data call transaction_lookup --args '{"tx_hash": "0x0e4fdf7d3742366a474d7fc6baa538e4cad839eaa95f2c798403bd47deb795db"}'
Wallet Trace
- Route
GET /v1/onchain/wallets/{address}/trace- CLI
vike-data wallet trace- MCP
wallet_trace· 4 credits- Cost
- 1 request of the hourly limit
Follow the money — expand at each hop to the top counterparties by USD volume, outward, inward or both, up to a chosen depth. Every node carries label, wallet_type and risk_flag (null when the address is unlabelled).
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Root wallet to trace from |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
depth | query | Hops to follow. Default 2, clamped to 1–5 |
width | query | Top N counterparties per hop. Default 5, clamped to 1–10 |
window | query | 24h | 7d | 30d | 90d | all. Default 30d; anything else is a 400 |
min_usd | query | Minimum USD per transfer to follow. Default 10,000 |
direction | query | out (where funds went) | in (where they came from) | both. Default both; anything else is a 400 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/wallets/0x47666fab8bd0ac7003bce3f5c3585383f09486e2/trace?depth=2&width=3&window=all&min_usd=100000"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/wallets/0x47666fab8bd0ac7003bce3f5c3585383f09486e2/trace?depth=2&width=3&window=all&min_usd=100000",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet trace 0x47666fab8bd0ac7003bce3f5c3585383f09486e2 --depth 2 --width 3 --window all --min-usd 100000
# any MCP client; or from the terminal:
vike-data call wallet_trace --args '{"address": "0x47666fab8bd0ac7003bce3f5c3585383f09486e2", "depth": 2, "width": 3, "window": "all", "min_usd": 100000}'
Label routes
Who is behind many addresses at once, and where one label comes from.
Wallet Batch Labels
- Route
POST /v1/onchain/labels/batch- CLI
vike-data wallet batch- MCP
wallet_batch_labels· 2 credits- Cost
- 1 request of the hourly limit
Enrich a list of addresses in one call: entity, wallet type, risk flag, 30-day net flow, 30-day volume and 30-day transaction count, one row per input address in input order. The 30-day figures are live (the last 30 calendar days, the same window as the wallet summary) and are null only for an address with no label (found: false); a labelled wallet with no transfer in the window reads 0. An address flagged exploit is never typed cex. A row whose address source provider_e knows also carries provider_e: a list of that source's labels (class, entity, basis, evidence, the Polymarket wallets it is, controls or funded, their volume and realized PnL), on every chain, since an address is the same on every EVM chain. It is one more source beside the label: it never changes entity, wallet_type or found.
Parameters
| Parameter | In | Description |
|---|---|---|
addressesrequired | query | Comma-separated addresses. Capped at 100 |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum. Labels and the 30-day flows are read from that chain's own tables. Any other name is a 400 |
Call it
curl -X POST -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/labels/batch?addresses=0x28c6c06298d514db089934071355e5743bf21d60,0xd8da6bf26964af9d7eed9e03e53415d37aa96045,0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4"
import os, requests
r = requests.post(
"https://data.vike.io/v1/onchain/labels/batch?addresses=0x28c6c06298d514db089934071355e5743bf21d60,0xd8da6bf26964af9d7eed9e03e53415d37aa96045,0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data wallet batch 0x28c6c06298d514db089934071355e5743bf21d60 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4
# any MCP client; or from the terminal:
vike-data call wallet_batch_labels --args '{"addresses": ["0x28c6c06298d514db089934071355e5743bf21d60", "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "0xeae7380dd4cef6fbd1144f49e4d1e6964258a4f4"]}'
Wallet Label Provenance
- Route
GET /v1/onchain/labels/{address}/provenance- CLI
vike-data labels- MCP
wallet_label_provenance· 1 credit- Cost
- 1 request of the hourly limit
Which source claimed what about this wallet. Degrades to fewer rows rather than erroring while one of its label sources is being refreshed. Source provider_e covers Polymarket wallets, the addresses that control or funded them, and the addresses that paid many of them (bridges, exchanges, reward payers), with basis (onchain_fact, or behaviour_inferred / behaviour_corroborated for a guess from behaviour), evidence, and for a wallet, controller or funder the wallets' count, volume and realized PnL. It never feeds consensus. found is true when any source knows the address, so an address only provider_e knows answers found: true with an all-null consensus.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | Wallet address, 0x-prefixed hex |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/labels/0x28c6c06298d514db089934071355e5743bf21d60/provenance"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/labels/0x28c6c06298d514db089934071355e5743bf21d60/provenance",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data labels 0x28c6c06298d514db089934071355e5743bf21d60
# any MCP client; or from the terminal:
vike-data call wallet_label_provenance --args '{"address": "0x28c6c06298d514db089934071355e5743bf21d60"}'
Token routes
Find a token, screen tokens, see who bought, sold and holds one.
Token Search
- Route
GET /v1/onchain/tokens/search- CLI
vike-data token search- MCP
token_search· 0 credits- Cost
- 1 request of the hourly limit
Resolve a symbol or name to the tokens we know about. token_address is 0x-prefixed lower-case hex, the spelling every other address in this namespace uses.
Parameters
| Parameter | In | Description |
|---|---|---|
qrequired | query | Symbol or name (case-insensitive); an exact symbol ranks first, then symbol prefix, symbol substring, name prefix. A contract address is not matched |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum. Any other name is a 400 |
limit | query | Default 10, max 100 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/tokens/search?q=Wrapped+Ether&limit=5"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/tokens/search?q=Wrapped+Ether&limit=5",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data token search "Wrapped Ether" --limit 5
# any MCP client; or from the terminal:
vike-data call token_search --args '{"query": "Wrapped Ether", "limit": 5}'
Token Screener
- Route
GET /v1/onchain/tokens/screener- CLI
vike-data token screener- MCP
token_screener· 3 credits- Cost
- 1 request of the hourly limit
Find tokens by activity within a window — rank by volume, net inflow, holder count or smart- money inflow.
Parameters
| Parameter | In | Description |
|---|---|---|
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
window | query | 24h | 7d | 30d | 90d. Default 24h |
sort | query | volume | net_inflow | holders | smart_inflow. Default volume. smart_inflow is ethereum only |
min_volume_usd | query | Drop tokens below this window volume |
min_holders | query | Drop tokens below this holder count |
smart_money_only | query | Boolean. Restrict to smart-money cohort flows. ethereum only: any other chain is a 400 |
limit | query | Route default 50; applied cap 100 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/tokens/screener?limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/tokens/screener?limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data token screener --limit 10
# any MCP client; or from the terminal:
vike-data call token_screener --args '{"limit": 10}'
Token Transfers
- Route
GET /v1/onchain/tokens/{token_address}/transfers- CLI
vike-data token transfers- MCP
token_transfers· 2 credits- Cost
- 1 request of the hourly limit
Who bought and who sold a token over a period — the top net buyers or the top net sellers, ranked by net USD, with quantities and USD values.
Parameters
| Parameter | In | Description |
|---|---|---|
token_addressrequired | path | Token contract address, 0x-prefixed |
period | query | 1h | 24h | 7d | 30d. Default 24h |
tab | query | bought (net buyers: bought more than sold) | sold (net sellers). Default sold |
chain | query | ethereum | bsc | base | hyperevm | all. Default ethereum |
hide_contracts | query | true leaves out the contracts we know of: wallets labelled as a pool, router, vault, bridge or staking contract, and addresses with contract code that bought and sold within 1% of each other (pass-through). Contract code is checked only for the wallets our pages rank, so a contract outside that set can still appear; one that mostly buys or sells (a vault, a smart wallet) stays. Default false |
min_amount | query | Count only transfers of at least this many tokens, in whole tokens (not raw units), up to 1e18. Applied to each transfer before a wallet's totals are summed: bought, sold and the USD columns add up only those transfers, and a wallet ranks on them alone |
max_amount | query | Count only transfers of at most this many tokens, in whole tokens, up to 1e18, applied per transfer like min_amount. A max_amount below min_amount is a 400 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/tokens/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/transfers?period=24h&chain=ethereum"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/tokens/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/transfers?period=24h&chain=ethereum",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data token transfers 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --period 24h --chain ethereum
# any MCP client; or from the terminal:
vike-data call token_transfers --args '{"token_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "period": "24h", "chain": "ethereum"}'
Token Holders
- Route
GET /v1/onchain/tokens/{token_address}/holders- CLI
vike-data token holders- MCP
token_holders· 2 credits- Cost
- 1 request of the hourly limit
The holder leaderboard for a token: by default ranked by cumulative net inflow USD, or by balance with sort=balance.
Parameters
| Parameter | In | Description |
|---|---|---|
token_addressrequired | path | Token contract address, 0x-prefixed |
chain | query | ethereum | bsc | base | hyperevm. Default ethereum |
limit | query | Route default 50; applied cap 50 |
sort | query | flow | balance. Default flow: ranked by cumulative net inflow USD (a flow, not a holding). balance: ranked by the on-chain balanceOf among candidate wallets chosen by net inflow and by volume, highest first; each row then also carries balance, value_usd, supply_pct, balance_basis, balance_approximate and candidate_coverage (how much of the supply the candidates explain). Not available on every chain (a 400 names it); for the native coin the ranking stays flow and every row says balance_basis: unsupported |
exclude_exchanges | query | Boolean. Drop wallets labelled as an exchange. On sort=flow it is applied to the top 200 candidates (at least), so a long list can come back shorter than limit |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/tokens/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/holders?chain=ethereum&limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/tokens/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/holders?chain=ethereum&limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data token holders 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --chain ethereum --limit 10
# any MCP client; or from the terminal:
vike-data call token_holders --args '{"token_address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "chain": "ethereum", "limit": 10}'
Live stream catch-up
The stored events of the live chain.transfers channel, for what a socket missed.
Stream catch-up
- Route
GET /v1/onchain/stream-events- CLI
- —
- MCP
stream_events· 1 credit- Cost
- 1 request of the hourly limit
Stored events of one live-stream channel after a cursor, oldest first: the REST catch-up of wss://stream.vike.io/v1/stream/onchain, for what a client missed while it was disconnected (events are kept 3 days). Pass the channel's filters as query parameters, as on the socket: a list as a comma list (wallet=0x..,0x..), a number as a number (min_usd=1000); an unknown filter, or one the channel does not take, is a 400 naming it.
Channels and their filters:
- chain.transfers: chain, contract, entity, min_amount, min_usd, token, wallet, wallet_type
Returns {events: [{cursor, event}], next_cursor, has_more}. event is exactly what the socket sent, so dedupe on event.event_id. Pass next_cursor back as cursor while has_more is true. A window in which the channel was idle (nobody subscribed) comes as an item {cursor, gap} in its place. A cursor older than the retention is a 410 naming the oldest cursor accepted; omit cursor to start there.
chain here takes the stream's chain names: eth, bsc, base, hyperevm.
Live, the same events arrive on the socket: vike-data stream onchain --channel chain.transfers. Filters, a real event and code: Whale alerts.
Parameters
| Parameter | In | Description |
|---|---|---|
channelrequired | query | chain.transfers |
cursor | query | next_cursor of the previous page or the cursor of a stream frame. Empty: the oldest retained event |
limit | query | Default 200, clamped to 1-1000 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/onchain/stream-events?channel=chain.transfers&limit=50"
import os, requests
r = requests.get(
"https://data.vike.io/v1/onchain/stream-events?channel=chain.transfers&limit=50",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
# any MCP client; or from the terminal:
vike-data call stream_events --args '{"channel": "chain.transfers", "limit": 50}'
What the field names do not tell you
/tokens/{token_address}/holders ranks wallets by their all-time net inflow of the token in USD unless you pass sort=balance; then it ranks by the balance read from the chain, with the USD value and the share of the supply. Holders →/wallets/{address}/pnl is a daily series of USD in minus USD out. Realized and unrealized profit per token is /wallets/{address}/positions.limit above a route's applied cap but within what it accepts is cut to the cap with no error; each parameter table names the cap.chain takes ethereum, bsc, base or hyperevm (Ethereum, BNB Chain, Base and HyperEVM), default ethereum. The screener's smart-money sort and filter are Ethereum only; label provenance answers for Ethereum addresses; on Base and HyperEVM wallet discovery defaults to window=all.chain you ask about; HyperEVM has few, so a miss there says little. risk_flag is an empty string on a clean row, and wallet_type describes behaviour, not ownership./wallets/{address}/first-funder returns the first coin and the first priced token a wallet received as two answers, either of which can be null; neither is proof of who funded it.Status codes
| Status | Meaning | Body |
|---|---|---|
| 400 | The input was rejected: an unknown chain, a malformed address, or a window or sort the chain does not serve. | {"detail": "…"} |
| 401 | A missing, invalid or deactivated key. | Turned away before the API runs, so the body can be an HTML page. Do not assume JSON. |
| 404 | No such route. | {"detail": "Not Found"} |
| 410 | The stream catch-up: a cursor older than the events kept; the answer names the oldest cursor it accepts. | {"detail": "…"} |
| 422 | A parameter failed validation: the wrong type, or outside the bounds the route declares. | {"detail": [ … ]} |
| 429 | Over the hourly limit, or over 200 requests a minute on one key: wait the Retry-After seconds. | Retry-After: 60 (hourly) or Retry-After: 1 (per-minute) header, JSON error body |
| 503 | The data store or a chain read failed. Not your request: retry with back-off. | {"detail": "upstream unavailable"} |
- A failed query is never a 200 with an empty body. An empty list means there were no rows.
- The
/v1/onchainroutes run through a shared tool layer, which reports its own failures in-band — so an internal error there can arrive as a 400 carrying the error text. - MCP answers a bad credential with HTTP 401, a
WWW-Authenticate: Bearer resource_metadata=...header (where an OAuth client starts discovery) and a JSON-RPC error (-32000, Invalid or missing API key). A rate-limit trip there is a real 429. Check the body, not just the status.