Skip to main content

Bitquery in One Page

This page is written to be handed to an AI assistant. It is deliberately self-contained: no navigation, no links to click through, every query written out in full. Paste the whole page into a model's context and it has enough to write correct Bitquery queries, pick the right endpoint, and avoid the mistakes that produce silently wrong answers.

If you are a human, this also works as a one-screen reference.


1. What Bitquery is

Bitquery indexes blockchain data — trades, transfers, balances, holders, transactions, events, contract calls, mempool — and serves it as GraphQL. You do not run nodes. You send a GraphQL document over HTTP for historical questions, or over WebSocket for a live stream.

There are two generations of the API and they are different products with different schemas. Choosing the wrong one is the single most common mistake.


2. Endpoints and authentication

PurposeEndpoint
V2 querieshttps://streaming.bitquery.io/graphql
V2 subscriptionswss://streaming.bitquery.io/graphql
V1 querieshttps://graphql.bitquery.io

Authentication is the same token on both:

Authorization: Bearer ory_at_YOUR_TOKEN
Content-Type: application/json

For WebSocket, use the graphql-transport-ws (or graphql-ws) subprotocol and pass the token either as an Authorization header or as ?token=... on the URL. Send connection_init, wait for connection_ack, then send subscribe.

Subscriptions only work over WebSocket. Posting a subscription document to the HTTP endpoint returns subscriptions must be sent over a websocket connection, not HTTP.


3. Which chain lives on which API

V2 covers exactly these chains and no others.

  • EVM root, network: argument — eth, bsc, base, arbitrum, optimism, matic, robinhood, arc, arc_testnet
  • Solana root — Solana
  • Tron root — Tron
  • Hyperliquid root — Hyperliquid perpetuals
  • Trading root — cross-chain prices and trades, no network argument

Arc mainnet uses EVM(network: arc); Arc testnet uses EVM(network: arc_testnet). Leave the dataset argument out on both unless a fresh probe confirms archive or combined. The mainnet also appears in the Trading cubes as network name Arc and network id bid:arc.

Everything else is V1 only: Bitcoin, Litecoin, Bitcoin Cash, Dogecoin, Dash, Zcash, Cardano, Ripple, Stellar, Algorand, Avalanche, Celo, Fantom, Cronos, Klaytn, Moonbeam.

V1 groups chains under a shared root with a network argument:

  • bitcoin(network: bitcoin | litecoin | bitcash | dogecoin | dash | zcash)
  • ethereum(network: avalanche | celo_mainnet | fantom | cronos | klaytn | moonbeam | ...)
  • cardano(network: cardano), ripple(network: ripple), stellar(network: stellar), algorand(network: algorand), solana(network: solana)

Ethereum, BSC, Tron and Polygon are being migrated off V1. Always query them on V2, even though the V1 schema still accepts them.

Root names carry the version. V1 roots are lowercase — ethereum(, tron(, solana(, bitcoin(. V2 roots are capitalised — EVM(, Tron(, Solana(, Trading. If you see a lowercase root, it is a V1 document and belongs on the V1 endpoint.

Streaming is a V2 feature. V1's schema does expose a subscription root, but only ethereum(network: ...), and there is no documented V1 WebSocket endpoint — use V2 for anything live. Chains that exist only on V1 and are not in the V1 EVM family — Bitcoin and its relatives, Cardano, Ripple, Stellar, Algorand — have no GraphQL stream at all.


4. The cubes — which one answers which question

A "cube" is a top-level dataset. Picking the right cube matters more than writing clever filters.

Trading (cross-chain, no network argument) — the primary source for prices and trades:

  • Trading.Pairs — OHLC and volume for one trading pair on one market. Use this to price a single token, with a rank filter (see §6).
  • Trading.Tokens — OHLC and volume for a token, blended across every pool it trades in.
  • Trading.Currencies — a currency across all of its token representations and chains.
  • Trading.Trades — individual normalised trades across chains.

EVM(network: ...)Balances, Blocks, Calls, DEXPoolEvents, DEXPoolSlippages, DEXTradeByTokens, DEXTrades, Events, Holders, MinerRewards, PredictionManagements, PredictionSettlements, PredictionTrades, TransactionBalances, Transactions, Transfers, Uncles.

SolanaBlocks, DEXOrders, DEXPools, DEXTradeByTokens, DEXTrades, Instructions, PerpetualFills, PerpetualMarketSummaries, PerpetualOrders, PerpetualPositions, PerpetualPrices, Rewards, TokenSupplyUpdates, Transactions, Transfers.

Note: Solana has no Balances cube. Solana balances are derived from Transfers.

Quick map from question to cube:

QuestionCube
What is this token worth right now?Trading.Pairs with rank 1
Candles for a chartTrading.Pairs (recent) or DEXTradeByTokens (older)
Every swap on a chainEVM.DEXTrades / Solana.DEXTrades
Swaps grouped per tokenDEXTradeByTokens
Who holds this tokenEVM.Holders
What does this wallet holdEVM.Balances
Token movementsTransfers
Raw transactionsTransactions
Decoded logs / contract callsEvents / Calls
Pool reserves, liquidity eventsDEXPoolEvents, DEXPools
Pending, not yet minedany EVM cube with mempool: true

5. Datasets and how far back data goes

Every V2 root takes a dataset argument:

  • realtime — the recent window. This is the default if you omit dataset.
  • archive — deep history.
  • combined — archive and realtime stitched together.

Retention on realtime is short and differs per cube: roughly 12 hours on Solana DEXTrades, about 7 days on Solana DEXTradeByTokens, a few days on EVM DEX and transfer cubes, and about 30 days on the Trading cubes.

Two things about this cause most "the data is wrong" reports:

  1. realtime does not error when you ask beyond its window. It silently returns fewer rows. A chart just starts late. Always check whether the window you asked for is inside retention.
  2. archive and combined are not deployed for every cube on every chain. When they are not, you get a ClickHouse error like no table can query <Cube> ... consider use realtime dataset. That is not a bug in your query, it means that table does not exist for that chain.

Self-serve plans query realtime. Querying archive or combined needs the historical data add-on on the plan.


6. Rules that keep answers correct

These are the mistakes that produce a plausible-looking wrong number rather than an error.

Price one token with Pairs and rank 1, not with Tokens. The Tokens price is volume-weighted across every pool, so a thin pool drags it away from the real market. Add Ranking: {Position: {eq: 1}} to take the token's top market only. Always pair it with Price: {IsQuotedInUsd: true} — the rank filter picks the market, not the denomination.

In the Trading cube, token addresses are stored lowercase and the filter is case-sensitive. A checksummed EVM address returns zero rows and no error. Lowercase every address you put in a Trading filter.

Balances are cumulative, so they cannot be answered from realtime alone. A balance read from the realtime window is the change over the last few hours, not the balance. Use archive or combined for anything balance- or holder-shaped. The same applies to holder counts, token ownership and net worth.

Use the Trading cube for recent data and DEXTradeByTokens for older. The Trading cubes cover roughly the last 30 days. Beyond that, rebuild the same numbers from raw trades with DEXTradeByTokens.

A bare EVM root means Ethereum. EVM { ... } without a network argument returns Ethereum data. Be explicit.

On V1, always bound a list query by date. V1 sorts the whole table otherwise and the query dies with ClickHouse Code: 241, memory limit exceeded. V1 has no relative-date filter, so pass the date as a variable and move it as needed.

Solana combined is currently broken — every Solana cube returns a 500 on dataset: combined. Use realtime for recent and archive for history.

BalanceUpdates is being retired. Do not write new queries against it.

Always filter Interval.Time.Duration on Tokens / Pairs / Currencies. The cube stores ten bar durations simultaneously (1, 3, 5, 10, 30, 60, 300, 900, 1800, 3600 seconds). Omitting the filter returns one row per duration and inflates any aggregate roughly ten-fold. An unsupported value returns [] with HTTP 200 and no error, so a typo looks like "no data".

On Trading.Trades, use AmountsInUsd.Quote for USD, not .Base. .Base is Amounts.Base x PriceInUsd, and PriceInUsd is a smoothed reference price rather than the executed one, so it diverges on fast-moving tokens.

Trading.Trades emits some exact duplicate legs. Deduplicate on (TransactionHeader.Hash, Trader.Address, Side, Amounts.Base) before reporting exact trade counts or volumes. A row is a swap leg, not a transaction — count distinct tx hashes.

Network filters take the API's display names, case-sensitively. Polygon is "Matic" and BSC is "Binance Smart Chain". A wrong value returns zero rows and no error.

maximum: / minimum: are argmax, not max. Field(maximum: Key) returns Field's value on the row where Key is largest. Selectors sharing one key come from the same row; mixing keys silently composes a row that never existed.

The Trading cube has no liquidity, reserves or TVL. No field, nothing in the filter surface. Pool depth comes from chain-level DEXPools (about 12 hours of history) or a cloud export.


7. Answering questions about a whole population

The most common mistake when planning a large analysis is assuming you need one query per token or per wallet. You do not. limitBy returns one row per entity across the entire filtered population — not a top-N sample:

{
Trading {
Tokens(
where: {
Token: { Network: { is: "Solana" } }
Interval: { Time: { Duration: { eq: 3600 } } }
Block: { Time: { since: "...", till: "..." } }
}
limit: { count: 25000, offset: 0 }
limitBy: { by: Token_Id, count: 1 }
) {
Token { Id Symbol }
first: Price { Ohlc { Open(minimum: Block_Time) } }
peak: Price { Ohlc { High(maximum: Price_Ohlc_High) } }
Volume { Usd }
Supply { MarketCap }
}
}
}

One request covers every token on the chain for that hour. Page with limit: {count: 25000, offset: N} and always supply orderBy — offset paging without a sort order silently duplicates and drops rows.

Filter on computed aggregates server-side with selectWhere, the cube's HAVING:

vol: sum(of: Volume_Usd, selectWhere: { gt: "1000000" })

The pipeline is where → GROUP BY → selectWhere → orderBy → limitBy → limit. Attach selectWhere to a metric — on a bare leaf field it is a silent no-op.

What cannot be expressed server-side: a window defined relative to each row's own event — "the 4th to 24th hour after each token's own first trade", for example. GraphQL filters absolute time ranges only. Pull per-interval rows for the whole population, then compute relative windows in your own store. That is a handful of requests, not one per token.

Also unavailable server-side: HAVING on a ratio, in/notIn on aggregates, and anything on a subscription — streams drop selectWhere, if and most statistics.

See Query Operators for the full surface, including concentration statistics and wallet-overlap.


8. Worked examples

Every query below was executed against the live API before being written down.

Price of one token, from its top market

query ($token: String!, $network: String!) {
Trading {
Pairs(
where: {
Token: { Address: { is: $token }, Network: { is: $network } }
Ranking: { Position: { eq: 1 } }
Interval: { Time: { Duration: { eq: 60 } } }
Price: { IsQuotedInUsd: true }
}
orderBy: { descending: Block_Time }
limit: { count: 1 }
) {
Token { Symbol Name Address Network }
QuoteToken { Symbol Address }
Market { Name Address }
Price { Ohlc { Open High Low Close } Average { Mean } IsQuotedInUsd }
Volume { Usd Base Quote }
Block { Time }
}
}
}

Variables — note the lowercase address:

{ "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", "network": "Ethereum" }

Network accepts Ethereum, Base, Binance Smart Chain, Arbitrum, Optimism, Solana.

Latest DEX trades for a token on an EVM chain

{
EVM(network: eth) {
DEXTrades(
limit: { count: 10 }
orderBy: { descending: Block_Time }
where: { Trade: { Buy: { Currency: { SmartContract: { is: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" } } } } }
) {
Block { Time }
Transaction { Hash }
Trade {
Dex { ProtocolName ProtocolFamily }
Buy { Amount Currency { Symbol SmartContract } Buyer }
Sell { Amount Currency { Symbol SmartContract } }
}
}
}
}

What a wallet holds

Balances are cumulative, so this uses combined:

query ($address: String!) {
EVM(network: eth, dataset: combined) {
Balances(where: { Balance: { Address: { is: $address } } }) {
Currency { Symbol Name SmartContract }
Balance { Amount(selectWhere: { gt: "0" }) AmountInUSD }
}
}
}

Token transfers for a wallet

{
EVM(network: eth) {
Transfers(
limit: { count: 25 }
orderBy: { descending: Block_Time }
where: { Transfer: { Sender: { is: "0x21a31ee1afc51d94c2efccaa2092ad1028285549" } } }
) {
Block { Time }
Transaction { Hash }
Transfer { Amount Currency { Symbol SmartContract } Sender Receiver }
}
}
}

Solana trades for a token

{
Solana {
DEXTradeByTokens(
limit: { count: 10 }
orderBy: { descending: Block_Time }
where: { Trade: { Currency: { MintAddress: { is: "So11111111111111111111111111111111111111112" } } } }
) {
Block { Time }
Trade {
Currency { Symbol MintAddress }
Side { Currency { Symbol MintAddress } }
Dex { ProtocolName }
Price
PriceInUSD
Amount
}
}
}
}

A live stream

Same document shape, subscription instead of query, sent over WebSocket:

subscription {
EVM(network: eth) {
DEXTrades {
Block { Time }
Trade {
Dex { ProtocolName }
Buy { Amount Currency { Symbol } }
Sell { Amount Currency { Symbol } }
}
}
}
}

A V1 chain — Bitcoin and its relatives

{
bitcoin(network: dogecoin) {
blocks(options: { limit: 10, desc: "height" }) {
height
timestamp { time(format: "%Y-%m-%d %H:%M:%S") }
transactionCount
}
}
}

Balance of a V1 UTXO address as of a date — received minus spent:

query ($address: String!, $asof: ISO8601DateTime) {
bitcoin(network: bitcoin) {
received: outputs(outputAddress: { is: $address }, date: { till: $asof }) {
value(calculate: sum)
count
}
spent: inputs(inputAddress: { is: $address }, date: { till: $asof }) {
value(calculate: sum)
count
}
}
}

Pending transactions, before they are mined

{
EVM(mempool: true, network: eth) {
Transfers(limit: { count: 10 }) {
Transaction { Hash From To }
Transfer { Amount Currency { Symbol } Sender Receiver }
}
}
}

9. Errors and what they actually mean

MessageMeaning
subscriptions must be sent over a websocket connection, not HTTPSend the document to wss://streaming.bitquery.io/graphql instead.
no table can query <Cube> ... consider use realtime datasetarchive/combined is not deployed for that cube on that chain.
access restricted: your plan only allows "realtime"The query asked for archive or combined; the plan needs the historical data add-on.
402 No active billing periodNo active plan or points on the account.
Code: 241 ... memory limit exceededA V1 list query with no date bound. Add one.
context deadline exceededThe server gave up. Narrow the filter or the time window.
Zero rows, no errorEither the window is outside realtime retention, or a Trading address filter was not lowercase.

10. Choosing quickly

  1. Is the chain on V2? If not, use V1 and expect no subscriptions.
  2. Is the question about price? Use Trading.Pairs with rank 1.
  3. Is it about balances, holders or ownership? Use archive/combined — never realtime.
  4. Is it older than about 30 days? Use DEXTradeByTokens, not the Trading cubes.
  5. Does it need to be live? Same document, subscription, over WebSocket.
Build with Bitquery

Ready to run this in production?

Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.