Data access · 8 min read
Blockchain data API: the four kinds, and how to query one
Four different things get called a blockchain data API, and they are not substitutes for each other. This page separates them in one table, then spends the rest of its length on a working query: one request against a public dataset, the same thing in about 50 lines of dependency-free Python, and what changes when the range grows from one block to a day.
Every command and every output below was run against portal.sqd.dev/datasets/ethereum-mainnet and pasted back unmodified.
1. The four kinds, and what each is for
The phrase covers four categories that answer different questions. Most confusion about which provider to use comes down to which of these you need.
- Node and RPC APIsRaw, undecoded data for one chain, one call at a timeSubmitting transactions, reading current state
- Indexed-data APIsDecoded history you can filter and range overAnalytics, backfills, anything historical
- Decentralized data networksThe same decoded history, served by independent operatorsMulti-chain history without one vendor in the path
- Market-data APIsPrices, candles, volumes, usually aggregated offchainPricing and charting, not onchain provenance
The rest of this page is about the second and third rows, because that is where the practical work is. An RPC endpoint is easy to reach and well documented; what people actually get stuck on is reading a range of history without making one request per block.
2. One request, one block
Start with the smallest useful thing: every USDC transfer in a single Ethereum block. USDC is 0xa0b8…eb48, and an ERC-20 Transfer has topic0 0xddf252ad…523b3ef. Nothing to install, nothing to sign up for.
One line of newline-delimited JSON comes back, one object per block. Reformatted here with the first log kept intact:
Block 21,000,000 was mined at 2024-10-19 13:45:47 UTC. The three topics are the event signature, the sender and the recipient; the amount sits in data as hex, six decimals for USDC, so 0x1c119c784 is 7,534.66 USDC. Note what the request did not need: no ABI, no schema, no deployed indexer, and no separate call to find out which transactions those logs belonged to.
3. The same thing in Python
No web3 library, no SDK, no dependencies. This is the whole program, and it covers a full day rather than one block:
Output, in about 3 seconds:
Two things in that script are worth calling out, because both cost time to discover.
The User-Agent header is load bearing. Without it, Python's default Python-urllib/3.x is rejected at the edge and you get a bare HTTP Error 403: Forbidden with no body, which reads like an authentication problem and is not one. Any identifying string fixes it. curl sets its own, which is why section 2 works unmodified.
The continuation loop is not optional. That is section 4.
4. What happens over a whole day
Ask for blocks 21,000,000 to 21,007,199, which is 7,200 blocks and 24.07 hours at Ethereum's 12.04-second average over that window, and the first response does not contain all of it. It stops at block 21,001,566 and ends there. No error, no truncation flag, no cursor header.
That is the behaviour to design around: a response covers as much of the requested range as fits, and you resume from the last block it returned. Take the last header.number, add one, ask again. Loop until you reach your toBlock. Code that ignores this looks like it works and silently reports a fraction of the answer, which is the worst failure mode available.
- Blocks requested7,200
- Block objects returned7,068
- Blocks with a USDC transfer7,064
- Transfer logs returned62,668
- HTTP requests needed5
- Blocks per response1,567 / 1,796 / 1,761 / 1,732 / 344
- Downloaded18.8 MB
- Wall clock3.2 s
The chunk sizes are the tell: they are not a round number of blocks, and they vary. The cut is made on response size, not block count, so a range over a busy contract returns fewer blocks per response than a quiet one. Do not hard-code a page size. Four of the 7,068 block objects carry no transfer at all: each response ends on the last block it covered, matched or not, so the client always knows where to resume.
Five requests for a day of one contract's transfers is the number to compare against whatever you are using now. The equivalent over JSON-RPC is bounded by two things this is not: eth_getLogs carries provider-imposed caps on block range and result count, so the loop is driven by those limits rather than by payload size, and whether a range this old is served at all depends on the provider's history retention. Logs come from receipts, which a full node keeps unless it prunes them; archive mode is about historical state, not old logs. Neither is a criticism of RPC; it is a different tool, built for the first row of the table in section 1.
5. Which kind answers which question
Match the category to the question before comparing providers inside it.
- What is this account's balance right nowNode or RPC API
- Send this transactionNode or RPC API
- Every transfer of this token last monthIndexed-data API
- Rebuild this protocol's history from genesisIndexed-data API
- The same query across twenty chainsDecentralized data network
- What was ETH worth on TuesdayMarket-data API
The queries on this page are the third and fourth rows. If you want to see the same request shape run against a chain other than Ethereum, one query shape, every VM covers the cross-chain case, and RPC vs indexed data goes further into the first two rows.
Frequently asked questions
What is a blockchain data API?
How do I query blockchain data in Python?
What is the difference between an RPC and a blockchain data API?
Why does my request return 403?
Can I get blockchain data without running a node?
What is the best blockchain data API?
Related guides
Run the query yourself
ethereum-mainnet is one of the public datasets on Portal. Change the address and the topic and the same request works.