BlockVectra is a multi-chain blockchain data provider: one API key for JSON-RPC on every chain we support, and the Data API where it is available. We run the indexing infrastructure and metering gateway so you don't have to run your own node.
## What you get [#what-you-get]
* **JSON-RPC**: standard EVM methods (available methods vary by chain, with `debug_trace*` on supported chains), metered by Compute Units (CU) per call.
* **Data API**: read-only REST endpoints over indexed chain data — blocks, transactions, token transfers and balances, NFTs, DEX activity, tokenized stocks, and more. Coverage and capabilities vary by chain.
* **Derived datasets** — token holders and balances, DEX trades and daily prices, NFT holdings, tokenized stocks, and dataset freshness, continuously indexed and queryable via the Data API. Trace, DEX and tokenized-stock datasets are available only on the chains that provide them; see [Supported Chains](/en/chains/).
## Get an API key [#get-an-api-key]
Sign in to the [Console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) using an Ethereum wallet or GitHub / Google to automatically provision an account and create an API key. A single key works across all supported chains.
Once you have a key, head to the [Quickstart](/en/quickstart/) to make your first call.
## Find your way around [#find-your-way-around]
* [Quickstart](/en/quickstart/) — first JSON-RPC and Data API calls, CU billing, common errors
* [Supported Chains](/en/chains/) — Chain IDs, endpoint URLs, and feature support matrix
* [API Reference](/en/api/json-rpc/) — JSON-RPC methods and Data API endpoints
* [Datasets](/en/datasets/) — derived datasets across supported chains
---
BlockVectra is a multi-chain blockchain data provider. With a single API key, you can call JSON-RPC on every chain we support, and the Data API where it is available, through one unified gateway.
## One key, all chains [#one-key-all-chains]
* **Universal key**: the same API key works on every blockchain network BlockVectra supports — no separate keys per chain.
* **Chain in the URL**: every request names its chain explicitly through the `{chain}` path segment.
* **Aggregated usage**: account balance, Compute Unit (CU) rate-limit buckets, and free-tier per-second request limits are shared across all chains.
* **Plan-based pricing**: the exact prices and limits come from your active plan configuration. See [Pricing](https://blockvectra.com/en/pricing/).
## URL templates [#url-templates]
Standard URL structures for calling JSON-RPC and the Data API:
| Service | Mode | URL template | Notes |
| -------- | --------------- | ---------------------------- | ------------------------------------------------------------ |
| JSON-RPC | Key in the path | `POST /v1/{chain}/{api_key}` | Simplest form; recommended for curl and HTTP clients |
| JSON-RPC | Key in a header | `POST /v1/{chain}` | Pass the key in the `x-api-key: {api_key}` request header |
| Data API | REST endpoints | `GET /v1/data/{chain}/…` | Pass the key in the `x-api-key` request header |
| Status | Public status | `GET /v1/status` | No key required; returns each public chain's status and head |
> **Note**: the legacy path `POST /v1/{api_key}` returns HTTP 404 with `error.data.reason: "unknown_chain"`, while a request without a chain segment (e.g. `POST /v1` or `POST /v1/`) returns HTTP 404 with an empty body. See [Quickstart](/en/quickstart/) for setup details.
## Supported chains matrix [#supported-chains-matrix]
The blockchain networks currently available on BlockVectra, with their service capabilities:
## Robinhood Chain [#robinhood-chain]
Robinhood Chain is the first blockchain network live on BlockVectra.
* **Chain ID**: `4663`
* **Chain name (`{chain}` in URLs)**: `robinhood_mainnet`
* **JSON-RPC**: standard EVM methods plus `debug_trace*` execution tracing.
* **Data API datasets**: blocks, transactions, ERC-20 transfers and balances, NFTs, DEX trades and prices, and tokenized stocks.
* **Trace coverage**: trace data is available from a certain block onward (none before it), with a small number of scattered gaps after that. A request that falls before the start block or inside a gap returns `422 no_coverage` (not billed).
* **L2-specific fields**: blocks and transactions include L2-specific fields.
## Ethereum [#ethereum]
Ethereum is in Beta on both JSON-RPC and the Data API.
* **Chain ID**: `1`
* **Chain name (`{chain}` in URLs)**: `eth_mainnet`, e.g. `POST /v1/eth_mainnet/{api_key}` or `GET /v1/data/eth_mainnet/…`. Same API key and balance as every other chain.
### JSON-RPC [#json-rpc]
* **Methods**: only the methods listed for Ethereum under [Method Policy](/en/api/json-rpc/#method-policy) are open. Any other method, including `debug_trace*`, returns `-32601`.
* **Recent data only**: state and blocks cover about the last 36 days. Historical state queries outside the window return `-32011` (not billed); block, receipt and log queries outside the window return `-32000 old data not available due to pruning` (not billed); a transaction looked up by hash outside the window returns `result: null`.
* **`eth_getLogs`**: at most 1000 blocks per request.
### Data API [#data-api]
* **Recent data only**: Ethereum provides roughly the last 30 days of data; the start block is `coverage.from_block` in `GET /v1/data/chains`. The window moves forward about once a week, and the span is at least 30 days (currently 30–38 days). A block number or window below the start returns `422 no_coverage`; a transaction hash that has left the window returns `404`.
* **When `from_block` is `null`**: the start is temporarily unknown. Do not treat it as `0` or as full history, and do not send historical requests (by block number, window or hash) for Ethereum until it is an integer again; they return `503 unavailable`. Other chains are not affected.
* **Concurrency**: Ethereum's Data API currently handles a small number of concurrent requests; when busy it returns `503 gateway_overloaded` with `Retry-After: 1` — retry with backoff.
* **Available**: blocks, transactions, address transactions, token transfers, token metadata, and data freshness. Address transactions and transfers only cover that window: a response that crosses its start is marked `meta.coverage: "partial"`.
* **Not available**: balances, token holders, NFT holdings, DEX trades and prices, tokenized stocks, and traces return `422 no_coverage` (not billed).
## Discovering chains programmatically [#discovering-chains-programmatically]
Use these public endpoints to discover supported chains and their status:
### 1. Gateway status (`GET /v1/status`) [#1-gateway-status-get-v1status]
A public endpoint that requires no API key (CORS `*`). It lists only publicly available chains and returns their status and indexed head:
```bash
curl -s "https://dev-api.blockvectra.network/v1/status"
```
Example response:
```json
{
"checked_at": "2026-09-28T07:40:00Z",
"gateway": { "status": "ok" },
"chains": [
{
"chain": "robinhood_mainnet",
"name": "Robinhood Chain",
"chain_id": 4663,
"jsonrpc": true,
"data": true,
"status": "ok",
"head": {
"block": 74600000,
"time": "2026-09-28T07:39:58Z",
"lag_seconds": 3
}
},
{
"chain": "eth_mainnet",
"name": "Ethereum",
"chain_id": 1,
"jsonrpc": true,
"data": true,
"status": "ok",
"head": {
"block": 26010000,
"time": "2026-09-28T07:39:59Z",
"lag_seconds": 1
}
}
]
}
```
Each entry's `name` is always the chain's English display name. `jsonrpc` and `data` report whether that chain is open for JSON-RPC and the Data API respectively; `status` is `"ok"` or `"unavailable"`. If a chain is syncing, calls to that chain return `-32010`; other chains are unaffected. On `/v1/status`, `status` reports whether JSON-RPC is healthy and `data_status` reports whether the Data API is healthy (`"ok"` or `"unavailable"`); in `/v1/data/chains`, a `coverage.has_data` of `false` means the chain has no indexed data yet.
### 2. Data API chains (`GET /v1/data/chains`) [#2-data-api-chains-get-v1datachains]
Requires an API key in the `x-api-key` header (billed at 1 CU). Returns the public chains the Data API serves, each with what it serves (`features`), the blocks its data covers (`coverage`), how `finalized_block` is derived (`finality`) and its per-request caps (`limits`):
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/chains" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
Example response:
```json
{
"data": [
{
"chain": "robinhood_mainnet",
"chain_slug": "ROBINHOOD_MAINNET",
"name": "Robinhood Chain",
"chain_id": 4663,
"chain_external_id": "eip155:4663",
"features": [
"blocks", "transactions", "address_transactions", "transfers", "token_metadata",
"balances", "holders", "nfts", "dex_swaps", "dex_prices", "stocks", "traces", "freshness"
],
"coverage": { "history_mode": "full", "from_block": 0, "traces_from_block": 72050949 },
"finality": { "model": "block_lag", "lag_blocks": 256 },
"limits": { "max_page_size": 500, "max_window_blocks": 100000, "max_pools_for_token": 200, "max_batch_addresses": 100 }
},
{
"chain": "eth_mainnet",
"chain_slug": "ETH_MAINNET",
"name": "Ethereum",
"chain_id": 1,
"chain_external_id": "eip155:1",
"features": ["blocks", "transactions", "address_transactions", "transfers", "token_metadata", "freshness"],
"coverage": { "history_mode": "window", "from_block": 26000000, "retention_days": 30, "traces_from_block": null },
"finality": { "model": "block_lag", "lag_blocks": 64 },
"limits": { "max_page_size": 500, "max_window_blocks": 100000, "max_pools_for_token": 200, "max_batch_addresses": 100 }
}
]
}
```
On a window chain such as `eth_mainnet`, `coverage.from_block` is the lowest block currently indexed; it moves forward about once a week as old data is pruned (`from_block` above is only a snapshot). It can be `null` while the start is temporarily unknown; then do not send historical requests for that chain (they return `503 unavailable`), and never treat `null` as `0` or full history. Full-history chains always return an integer. Always read it from this endpoint rather than hard-coding it.
Calling `/v1/data/{chain}/…` with an unknown or not-public chain returns HTTP `404` with `error.code` `not_found` (decided before the key check, not billed, and not rate-limited; the chain name must be the exact lowercase slug).
---
## Overview [#overview]
The Data API provides REST endpoints for querying indexed blockchain data — blocks, transactions, addresses, tokens, NFTs, DEX activity, tokenized stocks, and dataset freshness.
* **Base URL**: `https://dev-api.blockvectra.network/v1/data` — every route except `/chains` is prefixed with a chain identifier (e.g. `https://dev-api.blockvectra.network/v1/data/{chain}/…`)
* **Protocol**: HTTP `GET` (plus `POST` for batch token lookups at `/{chain}/tokens:batch`), JSON responses
* **Authentication**: API key required — pass your key in the `x-api-key` request header. Requests are metered and billed in Compute Units (CU); only 2xx successful responses are billed
* **Ethereum (Beta)**: roughly the last 30 days of data and a smaller set of datasets — see [Supported Chains → Ethereum](/en/chains/#ethereum)
Data API CU weights are listed on the [Pricing](https://blockvectra.com/en/pricing/) page and returned by `GET /v1/plans`. See [Quickstart → Call the Data API](/en/quickstart/#4-call-the-data-api) for example requests and response shapes.
## Chains [#chains]
The Data API serves indexed data scoped to each chain: `https://dev-api.blockvectra.network/v1/data/{chain}/…`.
Available datasets and features vary by chain; see [Supported Chains](/en/chains/) for the full capability matrix. `GET https://dev-api.blockvectra.network/v1/data/chains` reports each chain's `features`, `coverage`, `finality` and `limits`. Requests outside a dataset's coverage return HTTP `422 no_coverage` (not billed); an unknown or not-public chain returns HTTP `404` with `error.code` `not_found` (decided before the key check, not billed, and not rate-limited; chain names must be exact lowercase slugs).
## Errors [#errors]
Every error response is `{"error":{"code","message"}}`; only `409 not_indexed_yet` may add `indexed_through` (the highest indexed block on that chain), and it is absent when the chain has no indexed data yet. Codes customers hit most often:
| Status | `error.code` | Meaning | Action |
| ------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `402` | `insufficient_balance` | Paid balance or free grant exhausted (not billed) | Top up in the console or wait for free grant refill |
| `404` | `not_found` | Unknown or not-public `{chain}`, or the object does not exist | Fix the request |
| `409` | `not_indexed_yet` | The block number is above the indexed head (`indexed_through` says how far the chain has indexed), or the chain has no indexed data at all yet (no `indexed_through`) | With `indexed_through`, poll until your block is at or below it; without it, wait for the chain to start indexing |
| `409` | `finality_exceeded` | Block is indexed but above `finalized_block` (not yet reorg-safe) | Wait for finality, or read an older block |
| `422` | `no_coverage` | Permanent gap: the chain lacks that capability, or the block is before indexed/trace coverage | Change the request; retrying will not help |
| `429` | `rate_limited` | Key CU rate limit (response includes `Retry-After`) or account call rate limit (no `Retry-After`); not billed | Retry after `Retry-After` seconds |
| `429` | `cost_exceeds_burst` | A single request costs more than the key's burst capacity; no `Retry-After` (not billed) | Split the request; retrying as sent never succeeds |
| `503` | `unavailable` | Temporarily unavailable; the response carries `Retry-After`. Also returned for historical requests on a chain whose `coverage.from_block` is currently `null` | Retry after `Retry-After` seconds |
| `503` | `gateway_overloaded` | The chain is busy (Ethereum's Data API handles few concurrent requests); `Retry-After: 1` | Retry with backoff |
`409 not_indexed_yet` covers two situations. **Block above the indexed head**: the requested block is higher than what the chain has indexed, the response includes `indexed_through`, and the data may arrive later — retry. **No indexed data yet**: for a chain that has just launched and has no indexed data at all, the response has no `indexed_through` and `GET /v1/data/chains` reports that chain's `coverage.has_data` as `false`. Wait for the chain to start indexing; `/v1/status` (`data_status`) and `/v1/data/chains` (`coverage.has_data`) show the current state, and these values change automatically once indexing begins.
Only `2xx` responses are billed; errors never are.
## Endpoint index [#endpoint-index]
### Chain
- GET /chains — List supported chains
- GET /{chain}/blocks/{number} — Get a block by number
- GET /{chain}/blocks/hash/{hash} — Get a block by hash
- GET /{chain}/blocks/{number}/transactions — List a block's transactions
- GET /{chain}/transactions/{hash} — Get a transaction by hash
### Status
- GET /{chain}/status/freshness — Freshness and lag per dataset
### Addresses
- GET /{chain}/addresses/{address}/transactions — List an address's transactions
- GET /{chain}/addresses/{address}/transfers — List an address's token transfers
- GET /{chain}/addresses/{address}/balances — List an address's ERC-20 balances
### Tokens
- GET /{chain}/tokens/{token}/transfers — List a token contract's transfers
- GET /{chain}/tokens/{token}/holders — List a token's holders
- GET /{chain}/tokens/{token} — Get token metadata
- POST /{chain}/tokens:batch — Batch get token metadata
### NFTs
- GET /{chain}/nfts/{contract}/{token_id} — Get one NFT's owner/holders
- GET /{chain}/nfts — List NFTs owned by an address
### DEX
- GET /{chain}/dex/swaps — List DEX swaps by pool or token
- GET /{chain}/dex/prices — Daily DEX token prices
### Stocks
- GET /{chain}/stocks — Daily leaderboard of tokenized stocks
- GET /{chain}/stocks/{token} — Get one tokenized stock
### Traces
- GET /{chain}/blocks/{number}/traces — Historical callTracer trace tree for a whole block
- GET /{chain}/transactions/{hash}/trace — Historical callTracer trace tree for one transaction
---
## Overview [#overview]
JSON-RPC 2.0 access on every supported chain is metered through the BlockVectra gateway. All requests are counted in **Computation Units (CU)** and rate-limited per key.
* **Endpoint**: `POST /v1/{chain}/{api_key}` (key in the path) or `POST /v1/{chain}` (key in a header). For Robinhood Chain, `{chain}` is `robinhood_mainnet`: `https://dev-api.blockvectra.network/v1/robinhood_mainnet`. The same [API key](/en/quickstart/#1-get-an-api-key) works on every supported chain
* **Protocol**: HTTP `POST`, single call or batch (max 100 calls)
* **Metering**: A request's total CU cost counts against your key's burst capacity as soon as it arrives. Every accepted call that gets a response is charged at the method's published CU weight; the error-code table lists the cases that are not billed (see [Error Codes](#error-codes)). Billing is settled hourly (rounded down to whole units, remainder carries over, executed \~15 minutes after the period ends)
* **Availability**: Available methods vary by chain. On Robinhood Chain: `eth_*`, `net_*`, `web3_*`, `debug_trace*` (with exceptions; see below). For other chains, see [Supported Chains](/en/chains/).
* **Ethereum (Beta)**: its own method list and about 36 days of recent data — see [Supported Chains → Ethereum](/en/chains/#ethereum).
## Integration [#integration]
### 1. Get an API Key [#1-get-an-api-key]
See [Quickstart → Get an API key](/en/quickstart/#1-get-an-api-key).
### 2. Call JSON-RPC [#2-call-json-rpc]
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -X POST "https://dev-api.blockvectra.network/v1/robinhood_mainnet/$BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc": "2.0",
"method": "eth_blockNumber",
"params": [],
"id": 1
}'
```
```ts
// npx tsx example.mts
import { createPublicClient, http } from 'viem'
const apiKey = process.env.BLOCKVECTRA_API_KEY!
const client = createPublicClient({
transport: http(`https://dev-api.blockvectra.network/v1/robinhood_mainnet/${apiKey}`),
})
const blockNumber = await client.getBlockNumber()
console.log(blockNumber)
```
```python
# uv run --with web3 python example.py
import os
from web3 import Web3
api_key = os.environ["BLOCKVECTRA_API_KEY"]
w3 = Web3(Web3.HTTPProvider(f"https://dev-api.blockvectra.network/v1/robinhood_mainnet/{api_key}"))
print(w3.eth.block_number)
```
```go
// go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
apiKey := os.Getenv("BLOCKVECTRA_API_KEY")
client, err := ethclient.Dial("https://dev-api.blockvectra.network/v1/robinhood_mainnet/" + apiKey)
if err != nil {
log.Fatal(err)
}
blockNumber, err := client.BlockNumber(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(blockNumber)
}
```
Add to `Cargo.toml`:
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
```
```rust
// cargo run
use alloy::providers::{Provider, ProviderBuilder};
#[tokio::main]
async fn main() -> eyre::Result<()> {
let api_key = std::env::var("BLOCKVECTRA_API_KEY").unwrap_or_default();
let url = format!("https://dev-api.blockvectra.network/v1/robinhood_mainnet/{api_key}").parse()?;
let provider = ProviderBuilder::new().connect_http(url);
let block_number = provider.get_block_number().await?;
println!("{block_number}");
Ok(())
}
```
**Key passing** (priority order):
* URL path: `POST /v1/{chain}/{api_key}`
* Header on `POST /v1/{chain}`: `x-api-key: {api_key}` (overrides Bearer if non-empty)
* Header on `POST /v1/{chain}`: `Authorization: Bearer {api_key}`
### 3. Handle CU Costs [#3-handle-cu-costs]
Every method carries a **CU (Computation Unit) weight**; the cost of a request is the sum of the weights of all calls it contains. See the CU weight table below for per-method weights and default values, and the `Billed` column of the error codes table for which error conditions are billed. Usage is charged per account per hourly period, rounded down to whole billing units (1 unit = 1000 CU), with the remainder carrying over to the next period (across periods the total charged is `floor(total CU / 1000)`); settlement runs about 15 minutes after the period ends (for example: 508 CU carried over + 2557 CU consumed = 3065 CU charges 3 billing units, with 65 CU carried over).
## Common Calls [#common-calls]
Worked examples for common calls.
### Query logs (`eth_getLogs`) [#query-logs-eth_getlogs]
Filter a contract's logs over a recent block range — here, the ERC-20 `Transfer` event (topic `0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef`). See the [CU Metering Rules](#cu-metering-rules) table below for `eth_getLogs`'s current CU weight; the gateway rejects any range wider than that chain's `eth_getLogs` span limit. On Robinhood Chain the limit is **1000 blocks** (`-32602 eth_getLogs block range too large: max 1000 blocks`); for other chains, see [Supported Chains](/en/chains/).
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -X POST "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "eth_getLogs",
"params": [{
"fromBlock": "0x45a2409",
"toBlock": "0x45a2609",
"address": "0x1111111111111111111111111111111111111111",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
}],
"id": 1
}'
```
```ts
// npx tsx example.mts
import { createPublicClient, http } from 'viem'
const apiKey = process.env.BLOCKVECTRA_API_KEY!
const client = createPublicClient({
transport: http('https://dev-api.blockvectra.network/v1/robinhood_mainnet', { fetchOptions: { headers: { 'x-api-key': apiKey } } }),
})
const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
const latest = await client.getBlockNumber()
const logs = await client.getLogs({
address: '0x1111111111111111111111111111111111111111',
topics: [TRANSFER_TOPIC],
fromBlock: latest > 100n ? latest - 100n : 0n,
toBlock: latest,
})
console.log(logs.length)
```
```python
# uv run --with web3 python example.py
import os
from web3 import Web3
api_key = os.environ["BLOCKVECTRA_API_KEY"]
w3 = Web3(
Web3.HTTPProvider(
"https://dev-api.blockvectra.network/v1/robinhood_mainnet",
request_kwargs={"headers": {"Content-Type": "application/json", "x-api-key": api_key}},
)
)
TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
latest = w3.eth.block_number
logs = w3.eth.get_logs(
{
"address": "0x1111111111111111111111111111111111111111",
"topics": [TRANSFER_TOPIC],
"fromBlock": max(latest - 100, 0),
"toBlock": latest,
}
)
print(len(logs))
```
```go
// go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"math/big"
"os"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)
var transferTopic = common.HexToHash(
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
)
func main() {
apiKey := os.Getenv("BLOCKVECTRA_API_KEY")
ctx := context.Background()
rpcClient, err := rpc.DialOptions(ctx, "https://dev-api.blockvectra.network/v1/robinhood_mainnet", rpc.WithHeader("x-api-key", apiKey))
if err != nil {
log.Fatal(err)
}
client := ethclient.NewClient(rpcClient)
latest, err := client.BlockNumber(ctx)
if err != nil {
log.Fatal(err)
}
from := uint64(0)
if latest > 100 {
from = latest - 100
}
address := common.HexToAddress("0x1111111111111111111111111111111111111111")
logs, err := client.FilterLogs(ctx, ethereum.FilterQuery{
FromBlock: new(big.Int).SetUint64(from),
ToBlock: new(big.Int).SetUint64(latest),
Addresses: []common.Address{address},
Topics: [][]common.Hash{{transferTopic}},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(len(logs))
}
```
Add to `Cargo.toml`:
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
reqwest = { version = "0.13", default-features = false }
```
```rust
// cargo run
use alloy::primitives::{address, b256};
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::types::Filter;
const TRANSFER_TOPIC: alloy::primitives::B256 =
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
#[tokio::main]
async fn main() -> eyre::Result<()> {
let api_key = std::env::var("BLOCKVECTRA_API_KEY").unwrap_or_default();
let rpc_url: reqwest::Url = "https://dev-api.blockvectra.network/v1/robinhood_mainnet".parse()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("x-api-key", api_key.parse()?);
let http_client = reqwest::Client::builder().default_headers(headers).build()?;
let provider = ProviderBuilder::new().connect_reqwest(http_client, rpc_url);
let latest = provider.get_block_number().await?;
let from = latest.saturating_sub(100);
let filter = Filter::new()
.address(address!("1111111111111111111111111111111111111111"))
.event_signature(TRANSFER_TOPIC)
.from_block(from)
.to_block(latest);
let logs = provider.get_logs(&filter).await?;
println!("{}", logs.len());
Ok(())
}
```
### Trace a transaction (`debug_traceTransaction`) [#trace-a-transaction-debug_tracetransaction]
Trace a transaction's internal calls with the `callTracer`. See the [CU Metering Rules](#cu-metering-rules) table below for `debug_trace*`'s current CU weight. Like the other state-reading methods, it's rejected once the target block falls outside that chain's recent-state window (`-32011`). On Robinhood Chain the window is the chain head minus **900 blocks**; for other chains, see [Supported Chains](/en/chains/). On chains that provide traces, use the [Data API](/en/api/data/) for historical traces.
> **Note**: The transaction must be within the chain's recent-state window (on Robinhood Chain about the last 900 blocks, i.e. only minutes); older transactions return `-32011` (`historical state is not available beyond the most recent 900 blocks`).
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -X POST "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '{
"jsonrpc": "2.0",
"method": "debug_traceTransaction",
"params": ["0xYOUR_TRANSACTION_HASH", {"tracer": "callTracer"}],
"id": 1
}'
```
```ts
// npx tsx example.mts
import { createPublicClient, http } from 'viem'
const apiKey = process.env.BLOCKVECTRA_API_KEY!
const client = createPublicClient({
transport: http('https://dev-api.blockvectra.network/v1/robinhood_mainnet', { fetchOptions: { headers: { 'x-api-key': apiKey } } }),
})
const txHash = '0xYOUR_TRANSACTION_HASH'
const trace = await client.request({
method: 'debug_traceTransaction' as any,
params: [txHash, { tracer: 'callTracer' }] as any,
})
console.log(trace)
```
```python
# uv run --with web3 python example.py
import os
from web3 import Web3
api_key = os.environ["BLOCKVECTRA_API_KEY"]
w3 = Web3(
Web3.HTTPProvider(
"https://dev-api.blockvectra.network/v1/robinhood_mainnet",
request_kwargs={"headers": {"Content-Type": "application/json", "x-api-key": api_key}},
)
)
tx_hash = "0xYOUR_TRANSACTION_HASH"
trace = w3.manager.request_blocking(
"debug_traceTransaction", [tx_hash, {"tracer": "callTracer"}]
)
print(trace)
```
```go
// go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/rpc"
)
func main() {
apiKey := os.Getenv("BLOCKVECTRA_API_KEY")
ctx := context.Background()
client, err := rpc.DialOptions(ctx, "https://dev-api.blockvectra.network/v1/robinhood_mainnet", rpc.WithHeader("x-api-key", apiKey))
if err != nil {
log.Fatal(err)
}
txHash := "0xYOUR_TRANSACTION_HASH"
var trace json.RawMessage
err = client.CallContext(ctx, &trace, "debug_traceTransaction", txHash, map[string]string{
"tracer": "callTracer",
})
if err != nil {
log.Fatal(err)
}
fmt.Println(string(trace))
}
```
Add to `Cargo.toml`:
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
reqwest = { version = "0.13", default-features = false }
serde_json = "1"
```
```rust
// cargo run
use alloy::providers::{Provider, ProviderBuilder};
use serde_json::json;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let api_key = std::env::var("BLOCKVECTRA_API_KEY").unwrap_or_default();
let rpc_url: reqwest::Url = "https://dev-api.blockvectra.network/v1/robinhood_mainnet".parse()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("x-api-key", api_key.parse()?);
let http_client = reqwest::Client::builder().default_headers(headers).build()?;
let provider = ProviderBuilder::new().connect_reqwest(http_client, rpc_url);
let tx_hash = "0xYOUR_TRANSACTION_HASH";
let trace: serde_json::Value = provider
.client()
.request("debug_traceTransaction", (tx_hash, json!({ "tracer": "callTracer" })))
.await?;
println!("{trace}");
Ok(())
}
```
### Batch several calls [#batch-several-calls]
Send several calls in a single request — up to the batch limit noted above (max 100 calls per request). This example reads the block number, chain ID, and gas price in one round trip.
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -X POST "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '[
{"jsonrpc": "2.0", "method": "eth_blockNumber", "params": [], "id": 1},
{"jsonrpc": "2.0", "method": "eth_chainId", "params": [], "id": 2},
{"jsonrpc": "2.0", "method": "eth_gasPrice", "params": [], "id": 3}
]'
```
```ts
// npx tsx example.mts
import { createPublicClient, http } from 'viem'
const apiKey = process.env.BLOCKVECTRA_API_KEY!
const client = createPublicClient({
transport: http('https://dev-api.blockvectra.network/v1/robinhood_mainnet', {
batch: true,
fetchOptions: { headers: { 'x-api-key': apiKey } },
}),
})
// viem automatically merges these three calls into a single JSON-RPC batch request.
const [blockNumber, chainId, gasPrice] = await Promise.all([
client.getBlockNumber(),
client.getChainId(),
client.getGasPrice(),
])
console.log(blockNumber, chainId, gasPrice)
```
```python
# uv run --with web3 python example.py
import os
from web3 import Web3
api_key = os.environ["BLOCKVECTRA_API_KEY"]
w3 = Web3(
Web3.HTTPProvider(
"https://dev-api.blockvectra.network/v1/robinhood_mainnet",
request_kwargs={"headers": {"Content-Type": "application/json", "x-api-key": api_key}},
)
)
with w3.batch_requests() as batch:
batch.add(w3.eth.block_number)
batch.add(w3.eth.chain_id)
batch.add(w3.eth.gas_price)
block_number, chain_id, gas_price = batch.execute()
print(block_number, chain_id, gas_price)
```
```go
// go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rpc"
)
func main() {
apiKey := os.Getenv("BLOCKVECTRA_API_KEY")
ctx := context.Background()
client, err := rpc.DialOptions(ctx, "https://dev-api.blockvectra.network/v1/robinhood_mainnet", rpc.WithHeader("x-api-key", apiKey))
if err != nil {
log.Fatal(err)
}
var blockNumber, chainID, gasPrice hexutil.Big
batch := []rpc.BatchElem{
{Method: "eth_blockNumber", Result: &blockNumber},
{Method: "eth_chainId", Result: &chainID},
{Method: "eth_gasPrice", Result: &gasPrice},
}
if err := client.BatchCallContext(ctx, batch); err != nil {
log.Fatal(err)
}
for _, elem := range batch {
if elem.Error != nil {
log.Fatal(elem.Error)
}
}
fmt.Println(blockNumber.String(), chainID.String(), gasPrice.String())
}
```
Add to `Cargo.toml`:
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["full"] }
eyre = "0.6"
reqwest = { version = "0.13", default-features = false }
```
```rust
// cargo run
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::client::BatchRequest;
#[tokio::main]
async fn main() -> eyre::Result<()> {
let api_key = std::env::var("BLOCKVECTRA_API_KEY").unwrap_or_default();
let rpc_url: reqwest::Url = "https://dev-api.blockvectra.network/v1/robinhood_mainnet".parse()?;
let mut headers = reqwest::header::HeaderMap::new();
headers.insert("x-api-key", api_key.parse()?);
let http_client = reqwest::Client::builder().default_headers(headers).build()?;
let provider = ProviderBuilder::new().connect_reqwest(http_client, rpc_url);
let mut batch = BatchRequest::new(provider.client());
let no_params: [(); 0] = [];
let block_number = batch.add_call::<_, String>("eth_blockNumber", &no_params)?;
let chain_id = batch.add_call::<_, String>("eth_chainId", &no_params)?;
let gas_price = batch.add_call::<_, String>("eth_gasPrice", &no_params)?;
batch.send().await?;
println!("{} {} {}", block_number.await?, chain_id.await?, gas_price.await?);
Ok(())
}
```
If the gateway rejects the whole batch instead — insufficient balance, rate limiting, burst capacity, or more than 100 calls (see the [Error Codes](#error-codes) table below) — it returns a single JSON-RPC error object instead of an array; viem's `batch: true` mode then surfaces this as an opaque `UnknownRpcError`, so retry a single call to see the actual error.
## CU Metering Rules [#cu-metering-rules]
## Method Policy [#method-policy]
Available methods vary by chain; each chain's list is shown below, and [Supported Chains](/en/chains/) covers the rest. Only methods matching an allowed name or pattern are reachable; a handful of methods are explicitly blocked even though they match one (they return `-32601 method not available`).
**Limits**:
* **Batch**: max 100 calls per request; also limited by the key's CU burst, see below.
* **Request body**: max 2 MiB
* **CU burst**: each API key has a CU bucket (`cu_per_sec` refill, `burst_cu` capacity; shown per key in the console Keys table). A single request — including a whole JSON-RPC batch — whose total CU exceeds the key's burst capacity is rejected with `-32022 request_exceeds_burst` (`request cost CU exceeds burst capacity CU`); split it into smaller batches.
## Error Codes [#error-codes]
## Full OpenAPI Reference [#full-openapi-reference]
See the [Full OpenAPI reference](/en/api/json-rpc/reference/) for the complete machine-readable specification with all method signatures, request and response schemas, and parameter details rendered interactively.
---
## Endpoint index [#endpoint-index]
### JSON-RPC
- POST /{chain}/{apiKey} — JSON-RPC call (API key in the URL path)
- POST /{chain} — JSON-RPC call (API key in a header)
### Status
- GET /status — Public service status
### Chains
- GET /chains — Public chain list and parameters
---
BlockVectra continuously indexes supported blockchain networks into structured, queryable data. Which datasets are available depends on the chain. Query them through the [Data API](/en/api/data/); see the [Quickstart](/en/quickstart/) for examples.
## What's available [#whats-available]
* **Blocks & transactions** — block and transaction data, including event logs and execution traces (where supported).
* **Token transfers & balances** — ERC-20 transfer history and address balances.
* **Token holders** — holder indexes derived from transfer activity.
* **Tokenized stocks** — US stocks and ETFs issued on supported chains as tokens.
* **DEX trades & daily prices** — decentralized exchange trade activity and daily prices computed from it.
* **NFT holdings** — current holders and balances derived from NFT transfer activity.
* **Data freshness** — how far each dataset lags behind the indexed chain head.
## Chain × Dataset Matrix [#chain--dataset-matrix]
Availability of datasets across supported chains:
| Dataset | [Robinhood Chain RPC and Data API](https://blockvectra.com/en/chains/robinhood_mainnet/) (`robinhood_mainnet`) | [Ethereum RPC and Data API](https://blockvectra.com/en/chains/eth_mainnet/) (`eth_mainnet`, Beta) | Notes |
| -------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| Blocks & transactions | ✓ | ✓ (last \~30 days, no traces) | Block and transaction data |
| Token transfers & balances | ✓ | Transfers partial; balances ✗ | ERC-20 transfers and address balances |
| Token holders | ✓ | ✗ | Indexed from transfer activity |
| Tokenized stocks | ✓ | ✗ | US equities and ETFs issued on Robinhood Chain |
| DEX trades & daily prices | ✓ | ✗ | DEX swaps and daily volume/prices |
| NFT holdings | ✓ | ✗ | ERC-721 / ERC-1155 holdings |
| Data freshness | ✓ | ✓ | Lags relative to indexed chain head |
### Coverage notes [#coverage-notes]
* **Ethereum window**: transfers and address transactions only cover the last \~30 days; a response that crosses the window start is marked `meta.coverage: "partial"`.
* **Traces**: available on select chains only, from a certain block onward, with a small number of gaps (see [Supported Chains](/en/chains/)).
✗ means requests return `422 no_coverage` (not billed). Ethereum provides roughly the last 30 days of data; the start block is `coverage.from_block` in `GET /v1/data/chains`.
See [Supported Chains](/en/chains/) for endpoint URLs, chain IDs, and additional per-chain details.
## Next steps [#next-steps]
* [Quickstart](/en/quickstart/) — make your first JSON-RPC and Data API calls.
* [Data API reference](/en/api/data/) — endpoints, parameters, and response shapes.
* [Supported Chains](/en/chains/) — service capabilities and network identifiers.
---
Autonomous AI agents and Large Language Model (LLM) tools need predictable discovery and machine-readable specifications. BlockVectra ships machine context files, standard OpenAPI 3.1 specifications, and keyless public JSON endpoints, so an agent can inspect supported chains, check operational status, and issue RPC calls on its own.
## 1. Machine-readable context and specifications [#1-machine-readable-context-and-specifications]
BlockVectra publishes files aimed at LLM agents and developer tools:
### llms.txt indexes [#llmstxt-indexes]
Following the [llmstxt.org](https://llmstxt.org) convention, these files give agents a structured summary of the site and its endpoints:
* **Main site index**: [**WWW\_URL**/llms.txt](https://blockvectra.com/llms.txt) — overview of the main site, supported chains, pricing, and public APIs.
* **Documentation index**: [**DOCS\_URL**/llms.txt](https://docs.blockvectra.com/llms.txt) — catalog of every documentation page with its title and description.
### Full documentation file (`llms-full.txt`) [#full-documentation-file-llms-fulltxt]
* **Complete documentation**: [**DOCS\_URL**/llms-full.txt](https://docs.blockvectra.com/llms-full.txt) — the full text of every English documentation page in one plain-text Markdown file, with interactive components removed. It can be loaded into an agent's system prompt or ingested into a Retrieval-Augmented Generation (RAG) pipeline.
### Downloadable OpenAPI 3.1 specifications [#downloadable-openapi-31-specifications]
The documentation site serves two OpenAPI 3.1 YAML files that can be imported directly into agent frameworks, tool generators, or API clients:
* **JSON-RPC API specification**: [/openapi/json-rpc.yaml](https://docs.blockvectra.com/openapi/json-rpc.yaml) — supported methods, per-chain method policy, error responses, and Compute Unit metering.
* **Data API specification**: [/openapi/data.yaml](https://docs.blockvectra.com/openapi/data.yaml) — REST endpoint definitions for indexed blocks, transactions, transfers, balances, holders, and related datasets.
## 2. Public JSON endpoints (no key required) [#2-public-json-endpoints-no-key-required]
An agent can inspect available chains, live status, and plan parameters before sending any metered request. None of these endpoints needs an API key:
* `GET /v1/status` and `GET /v1/chains` are unauthenticated, unbilled, and not rate-limited.
* `GET /v1/plans` is public and unauthenticated.
All three send `Access-Control-Allow-Origin: *`.
### Service status (`GET /v1/status`) [#service-status-get-v1status]
Returns the service's readiness and the synchronization status of each public chain:
```bash
curl -s "$BLOCKVECTRA_API_BASE/v1/status"
```
Response fields:
* `checked_at`: when the snapshot was generated (RFC 3339 / ISO 8601 UTC).
* `gateway.status`: service running status. `ok` means the service is ready; `degraded` means balance-admission or API key data is not ready or has expired, so paid requests are rejected until it recovers. This value is independent of any chain's node status.
* `chains[]`: the chains served to the public:
* `chain`: chain slug (e.g. `robinhood_mainnet`).
* `name`: human-readable display name.
* `chain_id`: EIP-155 chain ID (decimal integer).
* `jsonrpc`: whether JSON-RPC is served.
* `data`: whether the Data API is served.
* `data_features`: Data API capabilities available for this chain (an empty array when `data` is `false`).
* `data_status`: Data API running status (`ok` or `unavailable`; present only when `data` is `true`).
* `status`: chain node status (`ok` or `unavailable`).
* `head`: latest block information — `block` (latest block height), `time` (block timestamp), and `lag_seconds` (how far the block time lags the current time) — or `null` when unknown.
Example response from the specification:
```json
{
"checked_at": "2026-09-28T12:00:00Z",
"gateway": {
"status": "ok"
},
"chains": [
{
"chain": "robinhood_mainnet", "name": "Robinhood Chain", "chain_id": 4663,
"jsonrpc": true,
"data": true,
"data_features": [
"blocks",
"transactions",
"address_transactions",
"transfers",
"token_metadata",
"freshness"
],
"status": "ok",
"head": {
"block": 73017329,
"time": "2026-09-28T11:59:58Z",
"lag_seconds": 2
}
}
]
}
```
### Chain parameters (`GET /v1/chains`) [#chain-parameters-get-v1chains]
Returns each public chain's static parameters and method policy:
```bash
curl -s "$BLOCKVECTRA_API_BASE/v1/chains"
```
Response fields:
* `chains[]`: public chains and their static parameters:
* `chain`: chain slug.
* `name`: human-readable display name.
* `chain_id`: EIP-155 chain ID.
* `jsonrpc`: whether JSON-RPC is served.
* `data`: whether the Data API is served.
* `methods`: method policy:
* `allow`: allowed methods or prefix wildcard patterns (e.g. `eth_*`, `debug_trace*`).
* `deny`: denied methods or prefix wildcard patterns (e.g. `eth_newFilter`). Denied methods take precedence over allowed ones.
* `max_logs_block_range`: maximum block span allowed in a single `eth_getLogs` request.
* `state_window_blocks`: historical state window in blocks; `null` when the full history is available.
* `info`: per-chain public extension data (reserved; currently an empty object `{}`).
Example response from the specification:
```json
{
"chains": [
{
"chain": "robinhood_mainnet", "name": "Robinhood Chain", "chain_id": 4663,
"jsonrpc": true,
"data": true,
"methods": {
"allow": [
"eth_*",
"net_*",
"web3_*",
"debug_trace*"
],
"deny": [
"eth_newFilter",
"eth_newBlockFilter",
"eth_newPendingTransactionFilter",
"eth_getFilterLogs",
"eth_getFilterChanges",
"eth_uninstallFilter",
"eth_subscribe",
"eth_unsubscribe"
]
},
"max_logs_block_range": 1000,
"state_window_blocks": 900,
"info": {}
}
]
}
```
### Plans and method weights (`GET /v1/plans`) [#plans-and-method-weights-get-v1plans]
Plan parameters are served by the console backend at `GET https://dev-console-api.blockvectra.network/v1/plans`. An agent can query this endpoint at runtime to read the active free-plan limits and the Compute Unit (CU) weight of each method:
* `free`: Free Plan parameters — `signup_units` (sign-up grant, in units), `monthly_units` (cycle refill watermark, in units), `window_days` (usage cycle length in days), and `max_calls_per_sec` (free-plan per-second call cap).
* `pricing`: paid-plan parameters — `units_per_usd` (units per 1 USD), `cu_per_unit` (CU per unit), and `min_topup_usd` (minimum top-up in USD).
* `method_weights`: per-call CU weights, each `{ "method": string, "cu_weight": number }`. `method` is an exact JSON-RPC method name, a prefix rule ending in `*` (e.g. `debug_trace*`), the `*` row used for unlisted methods, or a Data API operation such as `data.`. Weights are per method and are not split by chain.
## 3. Authentication and key security [#3-authentication-and-key-security]
Agents that issue RPC calls must follow these rules:
* **Authentication**: pass the API key in the `x-api-key` request header as `x-api-key: `, or put it in the path: `POST /v1/{chain}/{api_key}`. The same key works on every supported chain, and on the Data API where it is available.
* **Key security**: keep API keys in server-side environment variables (for example `BLOCKVECTRA_API_KEY`) or a secrets manager. Never embed a key in browser code or any client-side bundle. Endpoints do return `Access-Control-Allow-Origin: *`, but they are meant to be called by backend services rather than from the browser.
* **Metering and upgrades**: usage is metered in Compute Units (CU): each method consumes CU according to its weight, and balance, CU buckets, and free-plan rate limits are shared across all chains. After a paid top-up, the free plan's per-second call cap no longer applies; each key still has a CU rate limit and burst capacity. Unused Free Credits stay in your Credits and can still be used. See the [Pricing page](https://blockvectra.com/en/pricing/) for details.
## 4. Chain selection workflow for agents [#4-chain-selection-workflow-for-agents]
Before dispatching calls, an agent can follow these steps:
1. **Check the chain and its method policy**: call `GET /v1/chains`, confirm that the target chain exists and has `jsonrpc: true`, and that the method you plan to call is allowed by `methods.allow` and not denied by `methods.deny` (deny wins).
2. **Check live status**: call `GET /v1/status` and confirm that `gateway.status` is `ok` and that the target chain's `status` is `ok`; use `head.lag_seconds` to decide whether the chain's data is fresh enough for your use case. When a chain's node is not synced, every method except `eth_chainId` returns JSON-RPC error `-32010` (HTTP 200, not billed), so the agent can wait and retry or pick another chain.
3. **Send the request**: `POST /v1/{chain}` with the `x-api-key` header and a standard JSON-RPC body.
## 5. Minimal working example [#5-minimal-working-example]
The example below reads `/v1/chains` to pick a chain that allows `eth_blockNumber`, checks `/v1/status`, and then calls `eth_blockNumber` once.
```bash
# API host, without a trailing /v1
export BLOCKVECTRA_API_BASE=""
export BLOCKVECTRA_API_KEY="rgw_your_api_key"
# 1. List public chains and their method policy
curl -s "$BLOCKVECTRA_API_BASE/v1/chains"
# 2. Check the service and per-chain status
curl -s "$BLOCKVECTRA_API_BASE/v1/status"
# 3. Call eth_blockNumber on the chain you selected
curl -s "$BLOCKVECTRA_API_BASE/v1/robinhood_mainnet" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```
```typescript
const apiBase = process.env.BLOCKVECTRA_API_BASE;
const apiKey = process.env.BLOCKVECTRA_API_KEY;
if (!apiBase || !apiKey) {
throw new Error("Missing BLOCKVECTRA_API_BASE or BLOCKVECTRA_API_KEY");
}
type ChainFacts = {
chain: string;
jsonrpc: boolean;
methods: { allow: string[]; deny: string[] };
};
function matches(pattern: string, method: string): boolean {
if (pattern === "*") return true;
if (pattern.endsWith("*")) return method.startsWith(pattern.slice(0, -1));
return pattern === method;
}
// 1. Fetch the public chain directory
const chainsRes = await fetch(`${apiBase}/v1/chains`);
const { chains } = (await chainsRes.json()) as { chains: ChainFacts[] };
// 2. Select a chain that serves JSON-RPC and allows eth_blockNumber
const selected = chains.find(
(chain) =>
chain.jsonrpc &&
!chain.methods.deny.some((pattern) => matches(pattern, "eth_blockNumber")) &&
chain.methods.allow.some((pattern) => matches(pattern, "eth_blockNumber")),
);
if (!selected) {
throw new Error("No chain found that allows eth_blockNumber");
}
// 3. Confirm the service and the selected chain are ready
const statusRes = await fetch(`${apiBase}/v1/status`);
const status = await statusRes.json();
const chainStatus = status.chains?.find(
(chain: { chain: string }) => chain.chain === selected.chain,
);
if (status.gateway?.status !== "ok" || chainStatus?.status !== "ok") {
throw new Error(`Chain ${selected.chain} is currently unavailable`);
}
// 4. Call eth_blockNumber on the selected chain
const rpcRes = await fetch([apiBase, "v1", selected.chain].join("/"), {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_blockNumber",
params: [],
}),
});
console.log("Response:", await rpcRes.json());
```
```python
import os
import requests
api_base = os.environ["BLOCKVECTRA_API_BASE"]
api_key = os.environ["BLOCKVECTRA_API_KEY"]
def matches(pattern: str, method: str) -> bool:
if pattern == "*":
return True
if pattern.endswith("*"):
return method.startswith(pattern[:-1])
return pattern == method
# 1. Fetch the public chain directory
chains = requests.get(f"{api_base}/v1/chains").json()["chains"]
# 2. Select a chain that serves JSON-RPC and allows eth_blockNumber
selected = next(
(
chain
for chain in chains
if chain["jsonrpc"]
and not any(matches(p, "eth_blockNumber") for p in chain["methods"]["deny"])
and any(matches(p, "eth_blockNumber") for p in chain["methods"]["allow"])
),
None,
)
if selected is None:
raise RuntimeError("No chain found that allows eth_blockNumber")
# 3. Confirm the service and the selected chain are ready
status = requests.get(f"{api_base}/v1/status").json()
chain_status = next(
(c for c in status["chains"] if c["chain"] == selected["chain"]),
None,
)
if (
status["gateway"]["status"] != "ok"
or chain_status is None
or chain_status["status"] != "ok"
):
raise RuntimeError(f"Chain {selected['chain']} is currently unavailable")
# 4. Call eth_blockNumber on the selected chain
rpc_response = requests.post(
"/".join([api_base, "v1", selected["chain"]]),
headers={"Content-Type": "application/json", "x-api-key": api_key},
json={"jsonrpc": "2.0", "id": 1, "method": "eth_blockNumber", "params": []},
).json()
print("Response:", rpc_response)
```
A successful call returns a standard JSON-RPC response object (example from the specification):
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x45a27f1"
}
```
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
BlockVectra meters requests in Compute Units (CU). Calls are billed only after a response is obtained; units are not deducted when a request arrives. This guide summarizes the billing determination rules across HTTP status codes, JSON-RPC calls, and the Data API, along with recommended actions for developers.
## HTTP Status Codes and Billing Rules [#http-status-codes-and-billing-rules]
The billing determination and handling rules for HTTP-level responses are as follows:
| HTTP Status | Response Body | Scenario | Billed? | Recommended Action |
| ----------: | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 200 | JSON-RPC response (single or batch) | Normal response; all JSON-RPC layer errors (parse error, method rejection, guard, upstream failure, node error) are also 200 | Evaluated per call | Inspect `result` or `error` for each call; if an error is returned, see JSON-RPC error handling below |
| 204 | Empty | All calls in the request are notifications | Notifications are billed as normal | Notifications are accepted and processed in the background; no extra action required |
| 400 | Empty | Malformed HTTP message (cannot parse request line or headers, invalid chunked encoding), or more than 10 s between two reads of the request body | No | Check HTTP request syntax, headers, and transmission continuity |
| 402 | JSON, `-32020` | Insufficient balance, allowance exhausted (newly created keys return 503 rather than 402 before billing data is synchronized) | No | Go to the Console [billing page](https://console.blockvectra.com/en/billing/) to check your balance and top up |
| 403 | Empty | Methods other than `POST` or `OPTIONS` on `/v1/{chain}` or `/v1/{chain}/{api_key}` (regardless of whether chain name is known) | No | Change the HTTP request method to `POST` (or cross-origin `OPTIONS` preflight) |
| 404 | JSON, `-32600` (`reason = unknown_chain`) | `POST` to an unknown `{chain}` (request body not read, key not checked) | No | Check the chain name in the URL against [Supported Chains](/en/chains/) (must be exact lowercase slug) |
| 404 | empty body | Missing key on known chain, key unknown or disabled; unmatched path (e.g. `POST /v1`, `/v1/`, `POST /v1/{chain}/`) | No | Include chain in URL (`/v1/{chain}`) or provide an active API key in `x-api-key` header (brand-new or rotated keys take a few seconds to take effect; wait a moment and retry) |
| 408 | Empty | Exceeded 35 s from reading request headers to returning response | **Possible**: calls already forwarded to the node are billed as normal once the node responds | Do not unconditionally retry state-changing calls (e.g. `eth_sendRawTransaction`); a client disconnect does not cancel calls already forwarded |
| 413 | Empty | Request body > 2 MiB (2,097,152 bytes), checked after auth and balance admission | No | Keep request body under 2 MiB; split batches into smaller requests |
| 414 / 431 | Empty | URI too long (414) or request headers too large (431) | No | Shorten request URI or trim HTTP request headers |
| 429 | JSON, `-32005` or `-32022`; carries `Retry-After` when CU token bucket is depleted (`-32005`); account rate limit 429 does not carry it | Bucket balance depleted → `-32005`; single request CU exceeds burst capacity → `-32022`; account call rate limit depleted → `-32005`; calls in single request exceed limit → `-32022` | No | For `-32005` with `Retry-After`, wait the specified seconds before retrying; for `-32022`, split the request or reduce batch size (retrying as-is will never succeed) |
| 503 | JSON, `-32021`, with `Retry-After` | Billing data temporarily unavailable; server temporarily rejects request (not a balance issue, no need to top up); newly created keys return this until billing data syncs (usually a few seconds) | No | Not a balance issue, no need to top up; wait the seconds specified in `Retry-After` and retry |
> Note: When accessed through Cloudflare, Cloudflare may return 52x or 1015 error pages; these are not generated by the service.
## JSON-RPC Error Codes and Billing Rules [#json-rpc-error-codes-and-billing-rules]
The same error code can originate from the platform or the node, and **billing differs**:
* **Errors generated by the platform itself**: never billed;
* **Errors returned by the node**: passed through as-is and billed at the method weight, with only the node error codes listed below as exceptions.
### Rule Details [#rule-details]
* **Node errors not billed**: The node's `-32002` (batch timeout), `-32003` (batch response too large), and `-32600` (batch rejected as a whole) indicate that the node abandoned the call early and are not billed; notifications in the same batch are also not billed. `4444` indicates the requested block was pruned by the node (the node retains roughly 7 days of history), is not billed, and does not affect other calls in the batch. `-32000` (only for `historical state ... is not available`, and Erigon's `old data not available due to pruning...`) indicates the request falls outside the node's state history window (roughly 128 blocks), is not billed, and does not affect other calls in the batch. Erigon has a window of about 36 days, is not billed, and does not affect other calls in the batch.
* **Node errors billed**: Other errors returned by the node count as work done by the node and are billed at the method weight, such as `execution reverted` (`-32000` or `3` with `data`), the node's own `-32602 invalid argument`, or `-32601` returned for an `eth_*` method that passed platform admission but is not implemented by the node.
* **Balance admission and sync**: `-32020` indicates insufficient account balance and requires a top-up; `-32021` indicates billing data is temporarily unavailable and the request is temporarily rejected—this is not a balance issue, no need to top up, simply wait the seconds specified in `Retry-After` and retry. A newly created key returns `-32021` before the system reads billing data for its account (usually within a few seconds), not `-32020`; after reading, accounts with balance are accepted as normal, while accounts truly depleted or deleted receive `-32020` (402).
* **Upstream failures**: A `-32603` generated by the platform due to upstream communication failure or malformed response (`upstream unavailable`, `no response from upstream`, `malformed upstream response`) carries `data.reason: upstream_unavailable`; `internal gateway error` is generated only in extremely rare cases of internal task panic or cancellation, does not represent upstream failure, and carries no `data.reason`.
* **Notification billing**: Notifications (204) are billed as normal. Notifications are billed when the node processes the batch successfully: upstream HTTP 2xx; when the batch has calls with an id, at least one received a normal response and there are no `-32002`/`-32003`/`-32600` elements; requests containing only notifications are billed if the upstream returns 2xx with an empty body.
### JSON-RPC Error Codes Table [#json-rpc-error-codes-table]
| Code | Source | HTTP | Message | Reason | Billed? | Recommended Action |
| -----: | ----------- | ---: | ------------------------------------------------------------------------------ | --------------------------------------------------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| -32700 | BlockVectra | 200 | `parse error` | - | No (costs 1 CU rate-limit token) | Fix request JSON syntax |
| -32600 | BlockVectra | 200 | `invalid request` | `invalid_request` | No (costs 1 CU rate-limit token) | Fix JSON-RPC request syntax and structure |
| -32600 | BlockVectra | 200 | `batch too large: max calls` | `batch_too_large` (+max) | No | Split batch into calls under the limit (standard batch limit is 100) |
| -32600 | BlockVectra | 200 | `invalid request: ambiguous member name` | `invalid_request` | No | Remove duplicate or ambiguous member names in JSON objects |
| -32601 | BlockVectra | 200 | `method not available: ` | - | No | Call only methods allowed for this chain (see [Supported Chains](/en/chains/)) |
| -32600 | BlockVectra | 404 | `unknown chain` | `unknown_chain` | No | Check the chain name in the URL |
| -32602 | BlockVectra | 200 | `eth_getLogs block range too large: max blocks` | - | No | Narrow the `eth_getLogs` block range (limit defined per chain, e.g. 1000 blocks) |
| -32602 | BlockVectra | 200 | `tracer not allowed` | - | No | Use an allowed native tracer (`callTracer`, `flatCallTracer`, `prestateTracer`, `4byteTracer`, `noopTracer`, or omit) |
| -32602 | BlockVectra | 200 | `trace timeout not allowed` | - | No | Set a valid Go duration string with timeout ≤ 30s |
| -32010 | BlockVectra | 200 | `node is syncing; calls are temporarily unavailable` | - | No | Node is syncing, retry later (except for `eth_chainId`) |
| -32011 | BlockVectra | 200 | `historical state is not available beyond the most recent blocks` | - | No | Query a more recent block (target block must be within the state window; avoid safe/finalized/earliest tags) |
| -32000 | BlockVectra | 200 | `transaction not found` | `not_found` | No | Verify transaction hash (`0x` + 64 hex characters) |
| -32000 | BlockVectra | 200 | `block not found` | `not_found` | No | Verify block hash or number |
| -32000 | BlockVectra | 200 | `upstream response too large` | `response_too_large` | No | Narrow query scope or split requests |
| -32005 | BlockVectra | 200 | `gateway overloaded, retry later` | `overloaded` | No | Server temporarily overloaded, retry later |
| -32005 | BlockVectra | 429 | `rate limit exceeded` | `key_rate_limit` / `free_plan_call_limit` / `concurrency_limit` | No | Reduce request frequency; honour `Retry-After` when present |
| -32022 | BlockVectra | 429 | `request cost CU exceeds burst capacity CU` | `request_exceeds_burst` | No | Split request or batch so single-request CU is below burst capacity |
| -32022 | BlockVectra | 429 | `request has calls, exceeding the free-plan limit of calls per second` | `free_plan_batch_too_large` (+max) | No | Split batch to fit under per-second limit, or upgrade to a paid plan |
| -32603 | BlockVectra | 200 | `upstream unavailable` | `upstream_unavailable` | No | Upstream communication failure, retry later |
| -32603 | BlockVectra | 200 | `no response from upstream` | `upstream_unavailable` | No | Upstream did not respond, retry later |
| -32603 | BlockVectra | 200 | `malformed upstream response` | `upstream_unavailable` | No | Upstream response malformed, retry later |
| -32603 | BlockVectra | 200 | `internal gateway error` | - | No | Rare internal error, retry later |
| -32020 | BlockVectra | 402 | `insufficient balance` | `balance_exhausted` / `free_grant_exhausted` | No | Go to Console [billing page](https://console.blockvectra.com/en/billing/) to top up, or wait for free refill |
| -32021 | BlockVectra | 503 | `billing data temporarily unavailable` | - | No | Billing data syncing (not a balance issue); wait `Retry-After` seconds and retry |
| 4444 | Node | 200 | `pruned history unavailable` | - | No | Requested block was pruned by node (\~7-day history retention); not billed; does not affect batch |
| -32000 | Node | 200 | `historical state ... is not available` | - | No | Outside node state history window (\~128 blocks); not billed; does not affect batch |
| -32000 | Node | 200 | `old data not available due to pruning...` | - | No (Erigon, ETH mainnet) | Outside node history window (Erigon \~36-day window); not billed; does not affect batch |
| -32002 | Node | 200 | `` | - | No | Node timed out on batch and abandoned call; not billed; notifications in batch also not billed |
| -32003 | Node | 200 | `` | - | No | Node batch response too large and abandoned; not billed; notifications in batch also not billed |
| -32600 | Node | 200 | `` | - | No | Entire batch rejected by node; not billed; notifications in batch also not billed |
| Other | Node | 200 | `` | - | **Yes** (method weight) | Node performed work (e.g. `execution reverted`, node `-32602`, node `-32601`); check contract call parameters |
## Data API Billing Rules [#data-api-billing-rules]
The Data API wraps read-only chain data into REST endpoints. Its billing and error handling follow these rules:
### Rule Details [#rule-details-1]
* "A Data API request is billed only when it succeeds."
* "Requests are metered and billed in Compute Units (CU); only 2xx successful responses are billed."
* "Chain-specific operations that are unavailable (e.g. unsupported chain or outside trace coverage) return HTTP 422 and are not billed."
* "Requests outside our data coverage are not billed but may count toward rate limits."
* "There are two exceptions for 404 errors: an unknown or not-public chain returns HTTP 404 with `error.code` `not_found` (decided before the key check, not billed, and not rate-limited; chain names must be exact lowercase slugs), while a missing, unknown, or disabled API key gets HTTP 404 with an empty body (identical to JSON-RPC). Requests exceeding rate limits return HTTP 429 (`error.code` `rate_limited` with `data.reason` `key_rate_limit`), and exhausted balances return HTTP 402 (`error.code` `insufficient_balance`); neither is billed."
### Data API Status Codes Table [#data-api-status-codes-table]
| HTTP Status | Error Code / Scenario | Billed? | Recommended Action |
| ----------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| 200 | Successful data response | **Yes** (Data API operation CU weight) | Parse `data`, `meta`, and `next_cursor` in the response envelope |
| 400 | Request parameters malformed or missing required fields | No | Check and fix query or body parameters |
| 402 | Balance exhausted (`error.code: "insufficient_balance"`) | No | Go to Console [billing page](https://console.blockvectra.com/en/billing/) to check balance and top up |
| 404 | Unknown or not-public chain (`error.code: "not_found"`, decided before the key check; does not count against rate limits), requested object does not exist, or API key missing/unknown/disabled (empty body) | No | Check the chain slug in the URL (must be exact lowercase); pass a valid key in the `x-api-key` header |
| 409 | Requested block is above the current indexed height (`error.code: "not_indexed_yet"`, includes `indexed_through`) | No | Query blocks up to `indexed_through` or retry later |
| 409 | Requested block is above `finalized_block` (`error.code: "finality_exceeded"`) | No | Wait until the block is finalized, or query an older block |
| 422 | Chain-specific operation unavailable (e.g. unsupported chain or outside trace coverage, `error.code: "no_coverage"`) | No (counts toward rate limits) | Verify chain capabilities via `GET https://dev-api.blockvectra.network/v1/data/chains` to avoid queries outside coverage |
| 429 | Rate limit exceeded (`error.code: "rate_limited"`), or a single request costs more than the key's burst capacity (`error.code: "cost_exceeds_burst"`) | No | Reduce request frequency; split oversized requests (a burst-exceeding request will never succeed as sent) |
| 503 | Data service temporarily unavailable (`error.code: "unavailable"`), or the chain is busy (`error.code: "gateway_overloaded"`) | No | Retry later and honour `Retry-After` when present |
> **Testing note**: These rules were confirmed on 2026-09-30 with a new free-plan account on the production environment: rejected methods (`-32601`), a syncing node (`-32010`), and the Data API's `409 finality_exceeded` and `422 no_coverage` did not add to usage, while successful calls were metered at the weights published by `GET /v1/plans`. This is a single observation, not a guarantee.
## Pricing and Upgrades [#pricing-and-upgrades]
The specific cost for all billed calls is determined by published CU weights:
* To inspect weights for all methods and operations, see the [method weight table](https://blockvectra.com/en/pricing/) and the [JSON-RPC CU Metering Rules](/en/api/json-rpc/#cu-metering-rules).
* For plan pricing and settlement details, see the [Pricing page](https://blockvectra.com/en/pricing/).
* Upgrading to a paid plan: making a paid top-up removes the Free Plan's per-second call limit; each key remains subject to CU rate and burst limits.
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
## What the DEX Daily Prices dataset is [#what-the-dex-daily-prices-dataset-is]
BlockVectra's DEX dataset indexes decentralized exchange trade activity and computes aggregated daily pricing metrics. The DEX daily prices endpoint (`getDexPrices`) provides the daily volume-weighted average price (VWAP), price indicators (fields `first_price`, `last_price`, `min_price`, and `max_price`), and volume metrics for a specified token over a given date range.
Availability of this dataset varies across networks; chains providing this dataset are subject to the [Supported Chains](/en/chains/) page.
This endpoint is not paginated: all matching daily rows within the requested date span are returned directly in `data`, and `next_cursor` is never present. If your application requires granular swap transactions rather than daily aggregates, use `GET /{chain}/dex/swaps` (see the [Data API Reference](/en/api/data/)).
## Request parameters and specification limits [#request-parameters-and-specification-limits]
The endpoint route is `GET https://dev-api.blockvectra.network/v1/data/{chain}/dex/prices`. All requests require authentication by supplying your API key in the `x-api-key` header.
The endpoint accepts the following query parameters:
| Parameter | Location | Type | Required | Description |
| --------- | -------- | ----------- | -------- | ---------------------------------------------------------------------------- |
| `chain` | path | string | Yes | Chain identifier, e.g. `robinhood_mainnet` |
| `token` | query | string | Yes | 20-byte base token address, `0x` optional, either case |
| `quote` | query | string | No | Optional 20-byte quote token address to restrict to a single base/quote pair |
| `from` | query | date string | Yes | UTC start date, inclusive, `YYYY-MM-DD` |
| `to` | query | date string | Yes | UTC end date, inclusive, `YYYY-MM-DD`. `to - from` must be `<= 90` days |
### Specification constraints and error codes [#specification-constraints-and-error-codes]
When a request violates specification constraints, the API returns a structured error body `{"error":{"code","message"}}`:
* **HTTP 400 (`bad_request`)**: Missing required query parameters (`token`, `from`, or `to`), invalid `token`/`quote` address syntax, invalid `YYYY-MM-DD` calendar dates, or `from` is after `to`.
* **HTTP 409 (`span_exceeded`)**: `to - from` is more than 90 days.
* **HTTP 404 (`unknown_chain`)**: `{chain}` is not a chain listed by `GET /chains`.
* **HTTP 422 (`no_coverage`)**: The chain does not support the `dex_prices` dataset capability.
* **HTTP 503 (`unavailable`)**: Service temporarily unavailable; retry according to the `Retry-After` header.
## Request examples [#request-examples]
The following examples query daily DEX prices for a base token across September 2026:
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices?token=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&from=2026-09-01&to=2026-09-30" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const url = new URL("https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices");
url.searchParams.set("token", "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599");
url.searchParams.set("from", "2026-09-01");
url.searchParams.set("to", "2026-09-30");
const res = await fetch(url, {
headers: {
"x-api-key": process.env.BLOCKVECTRA_API_KEY!,
},
});
if (!res.ok) {
throw new Error(`Request failed with status ${res.status}`);
}
const body = await res.json();
console.log(body);
```
```python
import os
import requests
res = requests.get(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/dex/prices",
params={
"token": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
"from": "2026-09-01",
"to": "2026-09-30",
},
headers={
"x-api-key": os.environ["BLOCKVECTRA_API_KEY"],
},
)
res.raise_for_status()
print(res.json())
```
## Detailed field reference [#detailed-field-reference]
Each entry in `data` represents aggregated daily DEX metrics for the token pair on that UTC date:
### Token and quote assets [#token-and-quote-assets]
* `day` (string): UTC date in `YYYY-MM-DD` format.
* `token` (string): 20-byte base token address in lowercase `0x`-prefixed hex.
* `token_symbol` (string or `null`): Base token symbol.
* `token_name` (string or `null`): Base token display name.
* `quote_token` (string): Quote asset address. The all-zero address (`0x0000000000000000000000000000000000000000`) represents native ETH as the quote asset.
* `quote_symbol` (string or `null`): Quote asset symbol (`"ETH"` when `quote_token` is the all-zero address).
* `quote_name` (string or `null`): Quote asset display name (`"Ether"` when `quote_token` is the all-zero address).
* `base_decimals` (integer or `null`): Base token decimals (0–255).
* `quote_decimals` (integer or `null`): Quote asset decimals (`18` when `quote_token` is the all-zero address).
### Volume and trade counts [#volume-and-trade-counts]
* `swap_count` (integer): Swap count for this row.
* `base_volume_raw` (string): Atomic base volume as an unsigned integer decimal string (`UInt256String`).
* `quote_volume_raw` (string): Atomic quote volume as an unsigned integer decimal string (`UInt256String`).
* `base_volume` (string or `null`): Human-readable base token volume scaled by `base_decimals` as a `DecimalString`; `null` when `base_decimals` is unknown.
* `quote_volume` (string or `null`): Quote volume scaled by quote decimals as a `DecimalString`, or `null`.
### Price indicators and VWAP [#price-indicators-and-vwap]
* `vwap` (string or `null`): Volume-weighted average price as a `DecimalString`, or `null`.
* `first_price` (string or `null`): First price indicator as a `DecimalString`, or `null`.
* `last_price` (string or `null`): Last price indicator as a `DecimalString`, or `null`.
* `min_price` (string or `null`): Minimum price indicator as a `DecimalString`, or `null`.
* `max_price` (string or `null`): Maximum price indicator as a `DecimalString`, or `null`.
### Exact fraction fields [#exact-fraction-fields]
* `first_price_numerator` / `first_price_denominator` (string): Exact integer numerator and denominator for `first_price` (`UInt256String`).
* `last_price_numerator` / `last_price_denominator` (string): Exact integer numerator and denominator for `last_price` (`UInt256String`).
* `min_price_numerator` / `min_price_denominator` (string): Exact integer numerator and denominator for `min_price` (`UInt256String`).
* `max_price_numerator` / `max_price_denominator` (string): Exact integer numerator and denominator for `max_price` (`UInt256String`).
* `refreshed_at` (string): Refresh timestamp for this row (ISO-8601 UTC timestamp).
### Envelope metadata (`meta`) [#envelope-metadata-meta]
* `chain`: Chain identifier.
* `chain_slug`: Canonical uppercase chain slug.
* `chain_external_id`: CAIP-2 formatted chain identifier.
* `as_of_block`: The indexed head block number from which this response's finality watermark was computed (reported by this dataset, not checked against request parameters).
* `finalized_block`: Reorg-safety watermark block number (not consensus finality).
* `coverage`: Coverage classification (reports `"full"` for this endpoint).
* `refreshed_at`: Metadata refreshed timestamp.
## Why prices use exact numerators and denominators [#why-prices-use-exact-numerators-and-denominators]
On-chain DEX pricing originates from Automated Market Maker (AMM) liquidity pool reserve ratios or swap formulas.
Standard JSON numbers rely on IEEE-754 double-precision floats, which present precision limitations:
1. **Floating-point truncation and drift**: Float64 values provide only 53 bits of precision, and dividing token quantities yields rounding drift that compounds across calculations.
2. **Transport safety**: Formatting values as decimal strings (`UInt256String`) ensures numbers travel across HTTP without losing precision in JSON parsers.
By providing the exact integer numerator and denominator for price indicators, BlockVectra enables exact mathematical calculations without floating-point conversion. Quantitative models, arbitrage monitors, and financial accounting systems can evaluate prices and ratios without floating-point inaccuracies.
### Handling exact fractions in TypeScript (BigInt) [#handling-exact-fractions-in-typescript-bigint]
In TypeScript, you can use native `BigInt` for cross-multiplication comparisons and fixed-point conversions without floating-point conversion:
```ts
interface DexDailyPrice {
first_price_numerator: string;
first_price_denominator: string;
last_price_numerator: string;
last_price_denominator: string;
}
// 1. Ratio comparison without floating-point conversion: check if close price is higher than open price
// a / b > c / d is equivalent to a * d > c * b
export function isPriceUp(row: DexDailyPrice): boolean {
const openNum = BigInt(row.first_price_numerator);
const openDen = BigInt(row.first_price_denominator);
const closeNum = BigInt(row.last_price_numerator);
const closeDen = BigInt(row.last_price_denominator);
return closeNum * openDen > openNum * closeDen;
}
// 2. Convert fraction to a fixed-point decimal string with arbitrary scale (without floating-point loss)
export function fractionToFixedString(
numeratorStr: string,
denominatorStr: string,
decimals = 18
): string {
const num = BigInt(numeratorStr);
const den = BigInt(denominatorStr);
if (decimals === 0) {
return (num / den).toString();
}
const scaleFactor = 10n ** BigInt(decimals);
const scaled = (num * scaleFactor) / den;
const intPart = scaled / scaleFactor;
const remainder = scaled % scaleFactor;
const fracPart = remainder.toString().padStart(decimals, "0");
return `${intPart}.${fracPart}`;
}
```
### Handling exact fractions in Python [#handling-exact-fractions-in-python]
Python provides standard library modules built specifically for rational and decimal calculations: `fractions.Fraction` and `decimal.Decimal`.
```python
from decimal import Decimal, getcontext
from fractions import Fraction
# 1. Exact rational calculations with fractions.Fraction
open_price = Fraction(
int(row["first_price_numerator"]),
int(row["first_price_denominator"])
)
close_price = Fraction(
int(row["last_price_numerator"]),
int(row["last_price_denominator"])
)
# Exact price delta without floating-point rounding error
price_delta = close_price - open_price
print(f"Price delta (fraction): {price_delta}")
if open_price != 0:
percentage_change = (price_delta / open_price) * 100
print(f"Percentage change: {float(percentage_change):.4f}%")
# 2. Arbitrary-precision decimal arithmetic with decimal.Decimal
getcontext().prec = 50
if int(row["first_price_denominator"]) != 0:
open_decimal = Decimal(row["first_price_numerator"]) / Decimal(row["first_price_denominator"])
print(f"High-precision open: {open_decimal}")
```
## Backfilling one year of daily prices [#backfilling-one-year-of-daily-prices]
To backfill a year of data (365 days) within the 90-day span limit, divide the full date range into consecutive windows of at most 90 days and issue chunked requests:
```ts
interface DateSpan {
from: string;
to: string;
}
/**
* Split a large date range into consecutive spans of at most maxDays (default: 90)
*/
export function splitDateRange(startDateStr: string, endDateStr: string, maxDays = 90): DateSpan[] {
const spans: DateSpan[] = [];
let currentStart = new Date(startDateStr);
const end = new Date(endDateStr);
while (currentStart <= end) {
const chunkEnd = new Date(currentStart);
chunkEnd.setUTCDate(chunkEnd.getUTCDate() + (maxDays - 1));
const effectiveEnd = chunkEnd < end ? chunkEnd : end;
spans.push({
from: currentStart.toISOString().slice(0, 10),
to: effectiveEnd.toISOString().slice(0, 10),
});
const nextStart = new Date(effectiveEnd);
nextStart.setUTCDate(nextStart.getUTCDate() + 1);
currentStart = nextStart;
}
return spans;
}
/**
* Backfill token daily prices across multiple 90-day chunks
*/
export async function backfillTokenDailyPrices(
chain: string,
token: string,
startDate: string,
endDate: string,
apiKey: string
) {
const chunks = splitDateRange(startDate, endDate, 90);
const allDailyPrices = [];
for (const chunk of chunks) {
const url = new URL(`https://dev-api.blockvectra.network/v1/data/${chain}/dex/prices`);
url.searchParams.set("token", token);
url.searchParams.set("from", chunk.from);
url.searchParams.set("to", chunk.to);
const res = await fetch(url, {
headers: { "x-api-key": apiKey },
});
if (!res.ok) {
throw new Error(`Failed to fetch span ${chunk.from}..${chunk.to}: HTTP ${res.status}`);
}
const json = await res.json();
allDailyPrices.push(...json.data);
}
return allDailyPrices;
}
```
## Capacity and CU usage calculations [#capacity-and-cu-usage-calculations]
Every Data API endpoint meters consumption in Compute Units (CU). The per-call CU weight for `data.dex_prices` and the estimated consumption for token backfills are calculated below. All figures are computed at build time from active platform plan data:
When scaling your backfill volume or requiring higher request concurrency, top up your account balance in the [Console](https://console.blockvectra.com/en/login/) to upgrade to a paid account. For active rates and unit conversions, see the [Pricing page](https://blockvectra.com/en/pricing/).
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
## What the Free Plan is suited for [#what-the-free-plan-is-suited-for]
BlockVectra's Free Plan provides an out-of-the-box blockchain interface for developers. Without requiring a credit card, you can use it for local development, personal projects, lightweight automation bots, contract event tracking, and moderate-frequency chain data queries. With reasonable call scheduling and efficient endpoints, it usually covers most development testing and prototyping needs.
Billing is metered in Compute Units (CU): each method consumes CU according to its weight, with usage pooled across all chains, JSON-RPC, and the Data API. For detailed billing rules, see the [Pricing page](https://blockvectra.com/en/pricing/).
## Method weights and period capacity [#method-weights-and-period-capacity]
The table below calculates the estimated call capacity for common methods within a single usage window based on the active method weights and platform API data. All numbers are computed at build time from the platform API:
## Task examples and upgrade paths [#task-examples-and-upgrade-paths]
To help you assess usage, here are calculations for representative development tasks:
## Getting started and upgrading [#getting-started-and-upgrading]
The free quota is ideal for development, testing, and lightweight workloads. When your traffic expands and requires higher concurrency or more compute units, make a paid top-up in the [Console](https://console.blockvectra.com/en/login/) to upgrade to a paid account. After topping up, you are no longer constrained by the Free Plan's per-second call limit; each key remains subject to CU rate and burst limits, as described in the [JSON-RPC documentation](/en/api/json-rpc/#method-policy). Any unused Free Credits stay in your Credits and can still be used. For current rates and billing units, please see the [Pricing page](https://blockvectra.com/en/pricing/).
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
## Two ways to read logs and transfers [#two-ways-to-read-logs-and-transfers]
`eth_getLogs` is a JSON-RPC method: it returns block logs through the JSON-RPC endpoint. The Data API exposes token transfer history through two chain-scoped endpoints:
* `GET /{chain}/addresses/{address}/transfers` — transfers involving an address.
* `GET /{chain}/tokens/{token}/transfers` — transfers for a single token contract.
Both use the same API key and are metered in CU by method weight (see the weights below). Which one fits depends on how recent the data is, whether you need a block window, and how you paginate.
## Limits that apply to eth\_getLogs [#limits-that-apply-to-eth_getlogs]
`eth_getLogs` is bounded by per-chain limits that the public `GET /v1/chains` response publishes:
* **Block span**: `max_logs_block_range` is the maximum number of blocks a single `eth_getLogs` request may span. It differs by chain — read it from `GET /v1/chains` (chains are listed on [Supported Chains](/en/chains/)) instead of hardcoding it. A wider range is rejected with JSON-RPC error `-32602 eth_getLogs block range too large` (not billed).
* **Node sync**: while a chain's node is not synced, `eth_getLogs` — like every method except `eth_chainId` — returns `-32010`; the call is not forwarded and not billed.
* **State window**: the state window that `GET /v1/chains` reports as `state_window_blocks` applies to state-reading methods such as `eth_call` and `eth_getBalance`, not to `eth_getLogs`.
* **Node pruning**: block and log reads are not limited by the state window, but they are limited by the node's retained history. Data that has been pruned returns `4444 pruned history unavailable` (not billed).
When the `fromBlock` and `toBlock` filter fields are omitted or `null`, they default to `latest`.
WebSocket subscriptions are not supported: `eth_subscribe` returns `-32601 method not available`. To follow new events, poll `eth_getLogs` over the newest blocks.
## What the Data API transfers endpoints provide [#what-the-data-api-transfers-endpoints-provide]
The two endpoints require different parameters:
| Endpoint | `standard` | Block window |
| -------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /{chain}/addresses/{address}/transfers` | Required: `erc20` or `erc721`. `erc1155` returns `422 no_coverage` | `from_block` and `to_block` are both required. Results are ordered by `(block_number, log_index)` descending. `direction` (`in`, `out`, or `any`; default `any`) filters by direction, and `token` optionally restricts the results to one contract. |
| `GET /{chain}/tokens/{token}/transfers` | Required: `erc20`, `erc721`, or `erc1155` | `from_block` and `to_block` are optional. An absent `to_block` defaults to `finalized_block`; an explicit value above it is a hard `409`, with no `clamp` escape. |
### Pagination [#pagination]
Both endpoints are keyset-paginated:
* `limit` defaults to 50; values above 500 are clamped to 500, and `0` or a non-integer returns `400 bad_request`.
* `next_cursor` appears only when there is another page. On the last page the key is absent entirely, never `null`.
* Pass the returned value back as `cursor`, unchanged, to fetch the next page. A cursor is valid only for the chain, endpoint, and query parameters that issued it.
### Coverage and finality [#coverage-and-finality]
* Both endpoints belong to the `transfers` capability. A chain that does not provide it returns `422 no_coverage`. Chains that provide this dataset are subject to the [Supported Chains](/en/chains/) page.
* `GET /v1/data/chains` reports each chain's `coverage` (`history_mode`, `from_block`, and `retention_days` on window chains). A block window entirely before the chain's first indexed block is `422 no_coverage`; one that starts before it is served as far as it goes, with `meta.coverage = "partial"`. On window chains `coverage.from_block` moves forward — read it at runtime.
* Block-scoped responses only serve data at or below `meta.finalized_block`, the reorg-safety watermark (not a consensus finality signal), which trails `as_of_block` by a per-chain margin.
* For address transfers, a `to_block` above `finalized_block` is `409 finality_exceeded` unless `clamp=true` truncates it down to `finalized_block`; a window that is too wide is `409 window_too_large` unless `clamp=true`. Token transfers have no `clamp` escape.
Each transfer item contains `token`, `standard`, `from`, `to`, `block_number`, `block_timestamp`, `tx_hash`, `tx_index`, and `log_index`. ERC-20 items add `amount`; ERC-721 items add `token_id`; ERC-1155 items add `operator`, `token_id`, `value`, and `batch_index`.
## Which one to use [#which-one-to-use]
| Typical task | Better fit | Why |
| -------------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Events in the most recent few hundred blocks | `eth_getLogs` | One request can cover a recent range as long as it does not exceed that chain's `max_logs_block_range`. Unlike the transfers endpoints, it is not limited to blocks at or below `finalized_block`. |
| An address's historical transfers | `GET /{chain}/addresses/{address}/transfers` | Address-scoped query with a `from_block`/`to_block` window, `direction` and `token` filters, and cursor pagination; results stop at `finalized_block`. |
| All transfers of a token | `GET /{chain}/tokens/{token}/transfers` | Token-contract-scoped query covering `erc20`, `erc721`, and `erc1155`, with an optional window and cursor pagination for the full result set. |
| Live monitoring of new events | `eth_getLogs` (polling) | WebSocket subscriptions are not supported (`eth_subscribe` returns `-32601`), and the transfers endpoints only serve data at or below `finalized_block`. Poll `eth_getLogs` over the newest blocks. |
## Querying logs with eth\_getLogs [#querying-logs-with-eth_getlogs]
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
# fromBlock / toBlock default to latest. Set an explicit recent range to follow
# new events, and keep its span within the chain's max_logs_block_range.
curl -s "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [{
"address": "0x1111111111111111111111111111111111111111",
"fromBlock": "latest",
"toBlock": "latest"
}]
}'
```
```ts
const res = await fetch("https://dev-api.blockvectra.network/v1/robinhood_mainnet", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.BLOCKVECTRA_API_KEY!,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_getLogs",
params: [{
address: "0x1111111111111111111111111111111111111111",
fromBlock: "latest",
toBlock: "latest",
}],
}),
});
const { result } = await res.json();
console.log(result);
// npx tsx example.mts
```
```python
import os
import requests
res = requests.post(
"https://dev-api.blockvectra.network/v1/robinhood_mainnet",
headers={
"Content-Type": "application/json",
"x-api-key": os.environ["BLOCKVECTRA_API_KEY"],
},
json={
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getLogs",
"params": [{
"address": "0x1111111111111111111111111111111111111111",
"fromBlock": "latest",
"toBlock": "latest",
}],
},
)
res.raise_for_status()
print(res.json())
```
## Querying transfers with the Data API [#querying-transfers-with-the-data-api]
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
# from_block / to_block are optional here; omitting to_block defaults to finalized_block.
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111/transfers?standard=erc20" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
let cursor: string | undefined;
do {
const url = new URL(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111/transfers",
);
url.searchParams.set("standard", "erc20");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const body = await res.json();
console.log(body.data);
cursor = body.next_cursor; // absent on the last page
} while (cursor);
// npx tsx example.mts
```
```python
import os
import requests
url = "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/tokens/0x1111111111111111111111111111111111111111/transfers"
cursor = None
while True:
params = {"standard": "erc20"}
if cursor:
params["cursor"] = cursor
res = requests.get(
url,
params=params,
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
res.raise_for_status()
body = res.json()
print(body["data"])
cursor = body.get("next_cursor") # absent on the last page
if not cursor:
break
```
To query by address instead, `from_block` and `to_block` are required:
```bash
# clamp=true truncates a too-wide window, or a to_block above finalized_block,
# instead of returning 409.
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/transfers?standard=erc20&from_block=0&to_block=73000000&direction=any&clamp=true" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
## CU per call [#cu-per-call]
Every method is billed by its CU weight. The weights below are read from the platform plans API at build time:
For current prices and top-up options, see the [Pricing page](https://blockvectra.com/en/pricing/).
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
## 1. One key across all supported chains [#1-one-key-across-all-supported-chains]
The same API key works on all supported chains for JSON-RPC, and for the Data API on chains where it is available. Keys belong to your account and are not bound to a specific chain; there is no need to generate separate API keys for each network.
Credits and rate limits are shared across all networks and across the JSON-RPC API and the Data API; they are not split by network. For detailed billing rules, see the [Pricing page](https://blockvectra.com/en/pricing/).
* **Pooled balance**: Paid top-ups and free credits apply across all chains. Calls on any chain draw from the same account balance.
* **Pooled rate limits**: Compute Unit (CU) refill rates and burst capacities apply across all chains for a given key. Free Plan per-second call limits are pooled across all supported chains rather than split per chain.
* **Upgrade path**: After topping up, you are no longer constrained by the Free Plan's per-second call limit; each key remains subject to CU rate and burst limits, as described in the [JSON-RPC documentation](/en/api/json-rpc/#method-policy).
## 2. URL structure and the `{chain}` parameter [#2-url-structure-and-the-chain-parameter]
Every chain-scoped request specifies its target network in the URL path using `{chain}`. The `{chain}` parameter is the lowercase slug identifier of the chain (for example `robinhood_mainnet`).
| Service | Authentication | URL template | Description |
| ----------------- | --------------------- | ---------------------------- | --------------------------------------------------------------------- |
| JSON-RPC | Key in URL path | `POST /v1/{chain}/{api_key}` | Simplest form, suitable for curl and HTTP clients |
| JSON-RPC | Key in request header | `POST /v1/{chain}` | Pass key via `x-api-key: {api_key}` request header |
| Data API | REST routes | `GET /v1/data/{chain}/…` | Pass key via `x-api-key: {api_key}` request header |
| Public chain list | Unauthenticated | `GET /v1/chains` | Public list of chains and static facts (not billed, not rate-limited) |
| Public status | Unauthenticated | `GET /v1/status` | Current service status and chain heads (not billed, not rate-limited) |
`GET /v1/chains` reports a `jsonrpc` and a `data` flag for each chain. Address a chain with the JSON-RPC URLs when it serves JSON-RPC, and with `GET /v1/data/{chain}/…` when its `data` flag is `true` (the Data API serves only those chains).
> **Tip**: When passing your key via request headers, format the URL to end with the chain name, **without** a trailing slash. JSON-RPC is served exclusively at `/v1/{chain}` and `/v1/{chain}/{api_key}`. Requests with a trailing slash (such as `/v1/{chain}/`) or missing a chain segment return HTTP 404 with an empty body. Requests to an unknown `{chain}` return HTTP 404 with `error.data.reason: "unknown_chain"` (evaluated before reading the request body or key check, not billed, and not rate-limited).
## 3. Programmatic chain discovery and capabilities [#3-programmatic-chain-discovery-and-capabilities]
Supported chains and their capabilities are served dynamically. Do not hardcode a static list of chains in your application. Instead, discover available networks and their capabilities at runtime:
### Discover static facts via `GET /v1/chains` [#discover-static-facts-via-get-v1chains]
This public endpoint is unauthenticated, not billed, and not rate-limited, returning all publicly available chains:
```http
GET /v1/chains
```
Example response (from OpenAPI specification):
```json
{
"chains": [
{
"chain": "robinhood_mainnet", "name": "Robinhood Chain", "chain_id": 4663,
"jsonrpc": true,
"data": true,
"methods": {
"allow": ["eth_*", "net_*", "web3_*", "debug_trace*"],
"deny": ["eth_newFilter", "eth_newBlockFilter", "eth_newPendingTransactionFilter", "eth_getFilterLogs", "eth_getFilterChanges", "eth_uninstallFilter", "eth_subscribe", "eth_unsubscribe"]
},
"max_logs_block_range": 1000,
"state_window_blocks": 900,
"info": {}
}
]
}
```
Field reference:
* `chain`: Chain identifier slug (used for `{chain}` in URLs)
* `name`: Human-readable display name
* `chain_id`: EIP-155 chain ID (decimal integer)
* `jsonrpc`: Whether JSON-RPC is enabled
* `data`: Whether the Data API is enabled
* `methods`: JSON-RPC method policy for the chain, including `allow` (allowed methods or prefix wildcards) and `deny` (explicitly denied methods)
* `max_logs_block_range`: Maximum block range allowed in a single `eth_getLogs` request
* `state_window_blocks`: Historical state window size in blocks; `null` for full-history archive chains
* `info`: Reserved object for extended network information
### Check operational health via `GET /v1/status` [#check-operational-health-via-get-v1status]
This public endpoint is unauthenticated, not billed, and not rate-limited, returning service readiness and chain head information:
```http
GET /v1/status
```
Example response (from OpenAPI specification):
```json
{
"checked_at": "2026-09-28T12:00:00Z",
"gateway": {
"status": "ok"
},
"chains": [
{
"chain": "robinhood_mainnet", "name": "Robinhood Chain", "chain_id": 4663,
"jsonrpc": true,
"data": true,
"data_features": ["blocks", "transactions", "address_transactions", "transfers", "token_metadata", "freshness"],
"status": "ok",
"head": {
"block": 73017329,
"time": "2026-09-28T11:59:58Z",
"lag_seconds": 2
}
}
]
}
```
Field reference:
* `gateway.status`: Service readiness status (`ok` or `degraded`)
* `chains[].data_features`: Capabilities provided by the Data API for this chain
* `chains[].status`: Node operational status (`ok` or `unavailable`)
* `chains[].head`: Most recently polled block head (`block`, `time`, `lag_seconds`)
## 4. Per-chain differences to keep in mind [#4-per-chain-differences-to-keep-in-mind]
When switching between chains, review the fields provided in the specification and `GET /v1/chains`:
1. **Method allowance and policy (`methods.allow` / `methods.deny`)**: Available JSON-RPC methods vary by network according to their method policy. Requesting a disallowed method returns HTTP 200 with JSON-RPC error code `-32601` (`method not available`, not billed).
2. **Log block range (`max_logs_block_range`)**: Maximum block spans for `eth_getLogs` queries differ by chain. Exceeding the chain's limit returns HTTP 200 with JSON-RPC error code `-32602` (`eth_getLogs block range too large`, not billed).
3. **State retention window (`state_window_blocks`)**: Full-history chains return `null`. On chains with state pruning, historical state queries outside the window return HTTP 200 with JSON-RPC error code `-32011` (`historical state is not available beyond the most recent blocks`, not billed).
4. **Data API features and coverage (`data` / `data_features`)**: The chains that provide a dataset are listed on the [Supported Chains](/en/chains/) page. Querying a dataset a chain does not support, or a block before its indexed coverage, returns HTTP `422` (`error.code` `no_coverage`, not billed). When the service is temporarily unavailable — for example, when a chain is busy — requests return HTTP `503` with a `Retry-After` header (not billed).
## 5. Code examples [#5-code-examples]
The exact same code runs across different chains by updating the chain variable (or reading it from `GET /v1/chains`), querying `eth_blockNumber` via JSON-RPC and dataset freshness via the Data API:
```bash
export BLOCKVECTRA_API_KEY="rgw_your_api_key"
# Change the chain variable to target another chain from Supported Chains
CHAIN="robinhood_mainnet"
# 1. JSON-RPC: Query eth_blockNumber (POST /v1/{chain}, key in the x-api-key header).
# The default-chain endpoint ends with its chain slug; swap that last segment for $CHAIN.
RPC_URL="https://dev-api.blockvectra.network/v1/robinhood_mainnet"
RPC_URL="${RPC_URL%/*}/$CHAIN"
curl -s "$RPC_URL" \
-H "Content-Type: application/json" \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
# 2. Data API: Query dataset freshness (GET /v1/data/{chain}/status/freshness)
curl -s "https://dev-api.blockvectra.network/v1/data/$CHAIN/status/freshness" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
// Change this variable to target another chain, or read it dynamically from GET /v1/chains
const chain = "robinhood_mainnet";
const apiKey = process.env.BLOCKVECTRA_API_KEY!;
// 1. JSON-RPC: Call eth_blockNumber (POST /v1/{chain})
// The default-chain endpoint ends with its chain slug; swap that last segment for `chain`.
const defaultEndpoint = "https://dev-api.blockvectra.network/v1/robinhood_mainnet";
const rpcUrl = `${defaultEndpoint.slice(0, defaultEndpoint.lastIndexOf("/"))}/${chain}`;
const rpcResponse = await fetch(rpcUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_blockNumber",
params: [],
}),
});
const rpcResult = await rpcResponse.json();
console.log(`[${chain}] JSON-RPC blockNumber:`, rpcResult.result);
// 2. Data API: Query freshness (GET /v1/data/{chain}/status/freshness)
const dataUrl = `https://dev-api.blockvectra.network/v1/data/${chain}/status/freshness`;
const dataResponse = await fetch(dataUrl, {
headers: {
"x-api-key": apiKey,
},
});
const dataResult = await dataResponse.json();
console.log(`[${chain}] Data API freshness:`, dataResult.data);
```
```python
import os
import requests
# Change this variable to target another chain, or read it dynamically from GET /v1/chains
chain = "robinhood_mainnet"
api_key = os.environ["BLOCKVECTRA_API_KEY"]
# 1. JSON-RPC: Call eth_blockNumber (POST /v1/{chain})
# The default-chain endpoint ends with its chain slug; swap that last segment for `chain`.
default_endpoint = "https://dev-api.blockvectra.network/v1/robinhood_mainnet"
rpc_url = f"{default_endpoint.rsplit('/', 1)[0]}/{chain}"
headers = {
"Content-Type": "application/json",
"x-api-key": api_key,
}
rpc_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "eth_blockNumber",
"params": [],
}
rpc_resp = requests.post(rpc_url, json=rpc_payload, headers=headers)
print(f"[{chain}] JSON-RPC blockNumber:", rpc_resp.json().get("result"))
# 2. Data API: Query freshness (GET /v1/data/{chain}/status/freshness)
data_url = f"https://dev-api.blockvectra.network/v1/data/{chain}/status/freshness"
data_resp = requests.get(data_url, headers={"x-api-key": api_key})
print(f"[{chain}] Data API freshness:", data_resp.json().get("data"))
```
### Example responses (from specifications) [#example-responses-from-specifications]
JSON-RPC `eth_blockNumber` successful response (billed at the method's CU weight):
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": "0x45a27f1"
}
```
Data API `GET /v1/data/{chain}/status/freshness` successful response (billed in CU, only 2xx successful responses are billed):
```json
{
"data": [
{
"dataset": "blocks",
"category": "raw",
"max_block_number": 72313256,
"max_day": null,
"max_time": "2026-09-28T03:41:07Z",
"seconds_behind": 0,
"blocks_behind": 0,
"days_behind": null,
"checked_at": "2026-09-28T03:41:10Z"
},
{
"dataset": "traces",
"category": "raw",
"max_block_number": 72313256,
"max_day": null,
"max_time": "2026-09-28T03:41:07Z",
"seconds_behind": 0,
"blocks_behind": 0,
"days_behind": null,
"coverage_from_block": 72050949,
"coverage_to_block": 72313256,
"coverage_complete": true,
"checked_at": "2026-09-28T03:41:10Z"
},
{
"dataset": "dex_prices",
"category": "derived",
"max_block_number": null,
"max_day": "2026-09-27",
"max_time": "2026-09-27T00:00:00Z",
"seconds_behind": 99667,
"blocks_behind": null,
"days_behind": 1,
"checked_at": "2026-09-28T03:41:10Z"
}
],
"meta": {
"chain": "robinhood_mainnet",
"chain_slug": "ROBINHOOD_MAINNET",
"chain_external_id": "eip155:4663",
"as_of_block": 72313256,
"finalized_block": 72313000,
"coverage": "full",
"refreshed_at": "2026-09-28T03:41:10Z"
}
}
```
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
BlockVectra meters service usage on a per-method basis. Whether you query JSON-RPC methods or Data API endpoints across any supported chain, all usage is measured in standardized Compute Units (CU).
This guide walks through what a CU represents, the exact method weight resolution rules, how to convert CU weights into a price per million calls in USD, and how to estimate per-cycle costs.
## What is a CU and why per-method pricing [#what-is-a-cu-and-why-per-method-pricing]
According to the official Pricing page and the Terms of Service, CU and per-method metering are defined as follows:
* **Pricing page FAQ**: "A CU (Compute Unit) measures the resources a call consumes: different JSON-RPC methods consume different amounts of CU based on their weight — a higher weight means more load on the node." In addition, the **Pricing page intro** states: "Usage is metered in CUs (Compute Units): each JSON-RPC method consumes a set number of CUs based on its weight. Usage is totaled across all chains, JSON-RPC and Data API combined."
* **Terms of Service**: “"Compute unit" or "CU" means the unit in which we meter calls. The number of CU each method or data endpoint consumes (its "CU weight") is shown on the Pricing page.” Furthermore, “We meter your calls in CU: each method or data endpoint is metered at its CU weight. CU weights, the conversion between CU and billing units, and the price of billing units are as published on the Pricing page and in the Console at the relevant time, and may differ by method, product or network. Usage is totalled for each settlement period and deducted from your Credits; see the Pricing page for how settlement works. You can view your usage, charges and top-ups in the Console; usage figures may lag slightly.”
### Why per-method pricing [#why-per-method-pricing]
As the Pricing page puts it, "a higher weight means more load on the node." CU weights therefore reflect the relative load each method places on the node: a higher-weight method consumes more CU per call, and its price per million calls scales accordingly.
## Method weight resolution rules [#method-weight-resolution-rules]
When a client sends a request, how is its CU weight resolved? The API specification (`x-rpc-methods.cu.resolution`) defines the resolution precedence verbatim:
> `resolution: exact > longest prefix (pattern ending in *) > the '*' row`
The Pricing page explains this matching order: "Resolved in order: exact method match → the longest matching prefix rule ending in \* → the default weight for anything else."
The 3-tier matching process works as follows:
1. **Exact match**:
The system first checks the method weight table for an exact name match. For example, calls to `eth_call`, `eth_getLogs`, or `data.block` adopt the exact CU weight of their respective rows if a same-named row exists in the table.
2. **Longest prefix match (pattern ending in \*)**:
If no exact match exists, the system evaluates pattern rules ending with `*`, selecting the longest matching prefix. For instance, any debug trace method (such as `debug_traceTransaction`, `debug_traceCall`, or `debug_traceBlockByNumber`) matches the `debug_trace*` wildcard rule.
3. **Default wildcard match (the '\*' row)**:
If neither an exact match nor a prefix pattern matches, any unlisted JSON-RPC method falls back to the default `*` row weight.
*Note: The default `*` fallback applies only to JSON-RPC methods. Data API methods (prefixed with `data.`) do not use the default wildcard; requests outside available coverage return appropriate status codes and are not billed.*
## Converting CU per call to price per million calls [#converting-cu-per-call-to-price-per-million-calls]
When planning infrastructure budgets, developers often measure costs in dollars per million requests.
### Conversion formula [#conversion-formula]
Under the platform pricing model:
* Paid credits are denominated in **billing units**: `units_per_usd` (billing units per 1 USD).
* Each billing unit contains a fixed number of CUs: `cu_per_unit` (CUs per billing unit).
* Therefore, 1 USD provides `units_per_usd × cu_per_unit` total CUs.
When a method consumes a given CU weight per call, the price for one million (1,000,000) calls is calculated as:
```text
Price per 1M calls (USD) = weight × 1,000,000 ÷ (units_per_usd × cu_per_unit)
```
For how settlement works, see the [Pricing page](https://blockvectra.com/en/pricing/).
### Common methods price table [#common-methods-price-table]
The table below lists representative methods across matching rules, showing their resolved weights and list price per million calls. All values are read and computed at build time from the platform plans API (`GET /v1/plans`):
## Billing boundaries: what is not billed [#billing-boundaries-what-is-not-billed]
Understanding how CU weights are calculated is only half of the picture — developers also need to know **which calls are free**.
BlockVectra bills only upon response; early rejections are never charged. Because billing rules span HTTP status codes, JSON-RPC error codes, and Data API responses, this guide does not repeat every edge case. For the full table of rules and recommended developer actions, please refer directly to the dedicated guide:
👉 [What is not billed: error codes and billing rules](/en/guides/billing-rules/)
## How to estimate your per-cycle costs [#how-to-estimate-your-per-cycle-costs]
Estimating per-cycle costs helps teams choose between the Free Plan allowance and upgrading to paid capacity:
### 1. Identify your method distribution and frequency [#1-identify-your-method-distribution-and-frequency]
Break down your application's expected traffic by method:
* Lightweight polling or sync checks (such as `eth_blockNumber`);
* User-triggered contract calls (such as `eth_call`);
* Event indexing or historical transfer queries (such as `eth_getLogs` or Data API transfer endpoints).
### 2. Use the official usage estimator [#2-use-the-official-usage-estimator]
Rather than calculating manually, you can use the interactive estimator on the Pricing page:
👉 [Go to the Pricing page usage estimator](https://blockvectra.com/en/pricing/#estimate)
Select your methods and enter daily call volumes. The estimator will calculate:
* Total CU per usage cycle;
* Share of the Free Plan cycle allowance;
* Implied average calls per second;
* Estimated cost at list price for usage exceeding the free quota.
### 3. Review the Free Plan guide [#3-review-the-free-plan-guide]
To see what the free allowance covers, refer to the [Free Plan guide](/en/guides/free-plan/).
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
Data is derived from public on-chain records and is for informational purposes only. It does not constitute investment advice.
## What is the tokenized stocks dataset [#what-is-the-tokenized-stocks-dataset]
The BlockVectra Data API provides daily on-chain metrics and metadata for tokenized stocks. This dataset aggregates daily transfers, mints, burns, net supply changes, holder distributions, and decentralized exchange (DEX) trading metrics, enabling developers to track public activity for tokenized stocks.
For chains that offer this dataset, see the [Supported Chains](/en/chains/) page.
* **Base URL**: `https://dev-api.blockvectra.network/v1/data` — except for `GET /chains`, all Data API routes are prefixed with a chain identifier (e.g. `https://dev-api.blockvectra.network/v1/data/{chain}/…`)
* **Example chain**: `robinhood_mainnet` (used as an example path parameter; check [Supported Chains](/en/chains/) for all chains offering this dataset)
* **Authentication**: Provide your API key in the `x-api-key: ` request header
* **Billing and coverage**: Metered transparently in Compute Units (CU); only 2xx successful responses are billed. If a chain lacks stock coverage, the endpoint returns HTTP `422 no_coverage` (not billed)
## Daily leaderboard (`GET /{chain}/stocks`) [#daily-leaderboard-get-chainstocks]
The `GET /{chain}/stocks` endpoint returns a daily activity leaderboard of tokenized stocks for a specified UTC date, including display metadata (symbol, name, etc.), ordered by transfer activity descending (most active tokens first).
### Request parameters [#request-parameters]
* `{chain}` (path parameter, required): Chain identifier (for example, `robinhood_mainnet`).
* `day` (query parameter, optional): UTC calendar date in `YYYY-MM-DD` format. When omitted, defaults to the latest recorded day (if no activity is recorded, returns `200` with `data: []`). If provided but not a valid `YYYY-MM-DD` calendar date, returns HTTP `400` (`error.code = "bad_request"`).
* `limit` (query parameter, optional): Caps the number of records returned. Defaults to 50; values above 500 are clamped to 500; passing `0` or a non-integer returns HTTP `400` (`error.code = "bad_request"`).
### Pagination behavior [#pagination-behavior]
This endpoint is **not paginated**. The `limit` parameter caps the maximum number of records returned. In the enclosing `StockDailyListEnvelope` (`data` and `meta`), stock endpoints do not return `next_cursor` (the key is absent entirely, never `null`).
### Code examples [#code-examples]
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks?day=2026-09-29&limit=10" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const res = await fetch(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks?day=2026-09-29&limit=10",
{
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
},
);
const body = await res.json();
console.log(body);
```
```python
import os
import requests
res = requests.get(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks?day=2026-09-29&limit=10",
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
print(res.json())
```
### Response structure [#response-structure]
The response envelope is `StockDailyListEnvelope`, containing `data` and `meta`:
* `data` (array): A list of daily leaderboard records (`StockDaily`), ordered by transfer activity descending (most active tokens first). Each item includes token identifiers (`token`, `symbol`, `name`), transfer activity (`transfers`, `unique_senders`, `unique_receivers`), supply metrics (`mint_raw_amount`, `burn_raw_amount`, `net_supply_change`), distribution metrics (`holder_count`, `top10_holder_share_bps`), DEX trading metrics (`dex_swap_count`, `dex_raw_volume`), and refresh timestamp (`refreshed_at`).
* `meta` (object): Chain metadata (`chain`, `chain_slug`, `chain_external_id`, `as_of_block`, `finalized_block`, `coverage`, `refreshed_at`). Stock endpoints do not return `next_cursor`.
## Get one tokenized stock (`GET /{chain}/stocks/{token}`) [#get-one-tokenized-stock-get-chainstockstoken]
The `GET /{chain}/stocks/{token}` endpoint fetches metadata and up to 30 days of recent daily metrics for a specific tokenized stock by its token address.
### Request parameters [#request-parameters-1]
* `{chain}` (path parameter, required): Chain identifier (for example, `robinhood_mainnet`).
* `{token}` (path parameter, required): 20-byte token contract address; `0x` prefix is optional and either case is accepted (returned addresses are normalized to `0x` followed by 40 lowercase hex digits). An invalid address format returns HTTP `400` (`error.code = "bad_request"`).
* If `{token}` is not a known tokenized stock, returns HTTP `404` (`error.code = "not_found"`). If `{chain}` is an unknown chain, returns HTTP `404` (`error.code = "unknown_chain"`).
### Code examples [#code-examples-1]
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks/0x1111111111111111111111111111111111111111" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const token = "0x1111111111111111111111111111111111111111";
const res = await fetch(
`https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks/${token}`,
{
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
},
);
const body = await res.json();
console.log(body);
```
```python
import os
import requests
token = "0x1111111111111111111111111111111111111111"
res = requests.get(
f"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/stocks/{token}",
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
print(res.json())
```
### Response structure [#response-structure-1]
The response envelope is `StockTokenEnvelope`, containing `data` and `meta`:
* `data` (object): A `StockToken` object containing token contract metadata (`address`, `symbol`, `name`, `decimals`, `created_block`, `created_tx_hash`, `factory`, `creator`, `mint_address`, `burn_address`, `refreshed_at`) and recent daily metrics array `daily`.
* `daily` (array): An array of recent daily metrics (`StockDailyMetric`), up to 30 days, ordered by date descending (newest first). Each daily item shares the same metrics schema as the leaderboard above (without the redundant `token`, `symbol`, and `name` fields).
* `meta` (object): Chain metadata object consistent with the leaderboard response; stock endpoints do not return `next_cursor`.
## Key return fields explained [#key-return-fields-explained]
### Daily metric fields (StockDaily and StockDailyMetric) [#daily-metric-fields-stockdaily-and-stockdailymetric]
Both the leaderboard and single-token historical daily items include the following core fields:
| Field | Type | Description |
| ------------------------ | -------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `day` | `string` (date) | UTC aggregation date formatted as `YYYY-MM-DD`. |
| `token` | `string` (address) | Token contract address (present only in leaderboard `StockDaily`), 40 lowercase hex characters with `0x` prefix. |
| `symbol` | `string` | Token symbol (for example, `"EXMPL"`). |
| `name` | `string` | Token display name; empty string `""` when matching name metadata is unavailable. |
| `transfers` | `integer` (int64) | Total number of on-chain transfers on this UTC day. |
| `unique_senders` | `integer` (int64) | Number of unique sender addresses that initiated transfers on this day. |
| `unique_receivers` | `integer` (int64) | Number of unique recipient addresses that received transfers on this day. |
| `mint_raw_amount` | `string` (decimal) | Total raw token amount minted on this day. |
| `burn_raw_amount` | `string` (decimal) | Total raw token amount burned on this day. |
| `net_supply_change` | `string` (decimal) | Net supply change on this day (signed decimal string, may be negative). |
| `holder_count` | `integer` (int64) | Total holder address count. |
| `top10_holder_share_bps` | `integer` | Share of the top 10 holders in basis points (0–10000, 1 bps = 0.01%). |
| `dex_swap_count` | `integer` (int64) | Number of DEX swaps involving this token on this day. |
| `dex_raw_volume` | `string` (decimal) | Total DEX raw trading volume on this day. |
| `refreshed_at` | `string` (timestamp) | ISO-8601 UTC timestamp of when this daily record was last refreshed. |
### Token metadata fields (StockToken) [#token-metadata-fields-stocktoken]
When querying a single token, the outer `data` object contains contract metadata and recent daily metrics:
| Field | Type | Description |
| ----------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `address` | `string` (address) | Token contract address. |
| `symbol` | `string` | Token symbol. |
| `name` | `string` | Full token name. |
| `decimals` | `integer` or `null` | Token decimals (0–255), or `null` if unavailable. |
| `created_block` | `integer` (int64) | Block number in which the token contract was created. |
| `created_tx_hash` | `string` (hash) | Contract creation transaction hash, 64 lowercase hex characters with `0x` prefix. |
| `factory` | `string` (address) | Factory contract address. |
| `creator` | `string` (address) or `null` | Creator address, or `null` if unavailable. |
| `mint_address` | `string` (address) or `null` | Mint address, or `null` if unavailable. |
| `burn_address` | `string` (address) or `null` | Burn address, or `null` if unavailable. |
| `daily` | `array` | Array of recent daily metrics (`StockDailyMetric`), up to 30 days, ordered by date descending (newest first). |
| `refreshed_at` | `string` (timestamp) | ISO-8601 UTC timestamp of when token metadata was last refreshed. |
### Encoding conventions [#encoding-conventions]
The API adheres to strict encoding rules across all endpoints to preserve numerical precision and consistency:
* **Money-safety**: Any value that can exceed `2^53` (256-bit integers such as `mint_raw_amount`, `burn_raw_amount`, `net_supply_change`, and `dex_raw_volume`) is serialized as a **decimal string**, never a JSON number and never scientific or hex notation. This prevents precision loss in runtimes like JavaScript. In JavaScript/TypeScript, parse with `BigInt(str)` (e.g. `const net = BigInt(body.data.daily[0].net_supply_change)`); in Python, parse with `int(str)`. Counters staying well below `2^53` (`transfers`, `unique_senders`, `unique_receivers`, `holder_count`, `top10_holder_share_bps`, `dex_swap_count`, `created_block`) are plain JSON numbers.
* **Binary and hex values**: Addresses are `0x` followed by 40 lowercase hexadecimal characters; hashes are `0x` followed by 64 lowercase hexadecimal characters. All returned hex values are strictly lowercase.
* **Timestamps and dates**: Timestamps such as `refreshed_at` use `YYYY-MM-DDTHH:MM:SSZ` (ISO-8601 UTC with second precision). Daily aggregates (`day`) use plain calendar dates (`YYYY-MM-DD`).
## Pagination notes [#pagination-notes]
Stock endpoints do not return `next_cursor` (these endpoints are not paginated; other Data API endpoints that support pagination pass `next_cursor` back via the `cursor` parameter). `GET /{chain}/stocks` uses the `limit` parameter to cap the maximum number of records returned (up to 500); `GET /{chain}/stocks/{token}` returns up to 30 days of recent daily metrics in the `daily` array, ordered by date descending (newest first).
## Usage estimate (refreshing 50 tokens daily) [#usage-estimate-refreshing-50-tokens-daily]
Data API queries consume Compute Units (CU) based on platform method weights. The estimate below evaluates a scenario where 50 tokens each call `GET /{chain}/stocks/{token}` once daily, evaluated dynamically at build time against active method weights without hardcoded figures in prose:
## Getting started and upgrading [#getting-started-and-upgrading]
The free quota is ideal for development, testing, and lightweight workloads. When your traffic expands and requires higher concurrency or more compute units, make a paid top-up in the [Console](https://console.blockvectra.com/en/login/) to upgrade to a paid account. After topping up, you are no longer constrained by the Free Plan's per-second call limit; each key remains subject to CU rate and burst limits, as described in the [JSON-RPC documentation](/en/api/json-rpc/#method-policy). Any unused Free Credits stay in your Credits and can still be used. For current rates and billing units, please see the [Pricing page](https://blockvectra.com/en/pricing/).
## Next steps [#next-steps]
* [Browse the datasets directory](https://blockvectra.com/en/data/) to see every dataset BlockVectra indexes.
* [See the free plan and pricing](https://blockvectra.com/en/pricing/#free) to check what your account includes.
* [Log in to the console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) to create an API key.
---
This page shows the minimum you need to make your first authenticated call: how to get an
API key, how to choose a chain, how to call JSON-RPC, and how to shape a Data API request.
BlockVectra is now multi-chain. Every chain-scoped JSON-RPC or Data API request includes the chain name `{chain}` in the URL (for example `/v1/{chain}/{api_key}` or `/v1/data/{chain}/…`). The legacy path `/v1/{api_key}` returns HTTP 404 with `error.data.reason: "unknown_chain"`, while requests without a chain segment (such as `/v1` or `/v1/`) return HTTP 404 with an empty body. The same API key works on every supported chain.
## 1. Get an API key [#1-get-an-api-key]
Go to the [console](https://console.blockvectra.com/en/login/?next=%2Fen%2Fkeys%2F) and log in with GitHub, Google or an Ethereum wallet (your account is created on first login, and new accounts come with free credits; see the console and pricing page for details), then create an API key. You can send a test request right in the console to check that it works. The secret is only displayed once upon creation, so store it securely right away. Creating, rotating or revoking a key takes a few seconds to take effect everywhere; if a brand-new key returns 404 right away, wait a moment and retry.
Every key looks like `rgw_` followed by 64 hex characters, for example
`rgw_1f2e...` (truncated). Keep it secret — anyone with the key can spend your balance.
When your balance is insufficient, the gateway returns HTTP 402 (JSON-RPC error code `-32020`; Data API `error.code` `insufficient_balance`). Go to the console [Billing page](https://console.blockvectra.com/en/billing/) to check your balance and top-up methods.
## Choose a chain [#choose-a-chain]
Every BlockVectra endpoint is scoped to a chain: JSON-RPC requests carry the chain name `{chain}` in the URL path, and Data API requests prefix the route with it. See [Supported Chains](/en/chains/) for the chains currently available and their identifiers.
All examples on this page use `robinhood_mainnet`.
> **Tip**: swap `robinhood_mainnet` in any example URL for any `{chain}` from [Supported Chains](/en/chains/) to call that chain instead. The same API key works across all supported chains.
## 2. Call JSON-RPC [#2-call-json-rpc]
JSON-RPC endpoints are scoped to a chain: `POST /v1/{chain}/{api_key}` with the key in the
path, or `POST /v1/{chain}` with the key in the `x-api-key` header. `{chain}` is the chain
name the Data API also uses; for Robinhood Chain it is `robinhood_mainnet`, so the endpoint
on this page is `https://dev-api.blockvectra.network/v1/robinhood_mainnet`. The same API key works on every supported chain. There is no
WebSocket support. The API sends `Access-Control-Allow-Origin: *`, but you should keep your API key secret and make requests from a backend service rather than client-side browser code.
You can pass the key in one of two ways.
### Key in the URL path [#key-in-the-url-path]
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -s "https://dev-api.blockvectra.network/v1/robinhood_mainnet/$BLOCKVECTRA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```
```ts
import { createPublicClient, http } from "viem";
const key = process.env.BLOCKVECTRA_API_KEY!;
const client = createPublicClient({
transport: http(`https://dev-api.blockvectra.network/v1/robinhood_mainnet/${key}`),
});
console.log(await client.getBlockNumber());
// Run with: npx tsx example.mts
```
```python
import os
from web3 import Web3
w3 = Web3(Web3.HTTPProvider("https://dev-api.blockvectra.network/v1/robinhood_mainnet/" + os.environ["BLOCKVECTRA_API_KEY"]))
print(w3.eth.block_number)
```
```go
// Run with: go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
client, err := ethclient.Dial("https://dev-api.blockvectra.network/v1/robinhood_mainnet/" + os.Getenv("BLOCKVECTRA_API_KEY"))
if err != nil {
log.Fatal(err)
}
number, err := client.BlockNumber(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(number)
}
```
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```
```rust
use alloy::providers::{Provider, ProviderBuilder};
#[tokio::main]
async fn main() -> Result<(), Box> {
let key = std::env::var("BLOCKVECTRA_API_KEY")?;
let url = format!("https://dev-api.blockvectra.network/v1/robinhood_mainnet/{key}");
let provider = ProviderBuilder::new().connect_http(url.parse()?);
println!("{}", provider.get_block_number().await?);
Ok(())
}
```
### Key in a request header [#key-in-a-request-header]
```bash
export BLOCKVECTRA_API_KEY=rgw_your_api_key
curl -s "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```
```ts
import { createPublicClient, http } from "viem";
const key = process.env.BLOCKVECTRA_API_KEY!;
const client = createPublicClient({
transport: http("https://dev-api.blockvectra.network/v1/robinhood_mainnet", {
fetchOptions: { headers: { "x-api-key": key } },
}),
});
console.log(await client.getBlockNumber());
// Run with: npx tsx example.mts
```
```python
import os
from web3 import Web3
key = os.environ["BLOCKVECTRA_API_KEY"]
# request_kwargs replaces the provider's default headers entirely, so
# Content-Type must be repeated here or the gateway can't parse the body.
headers = {"Content-Type": "application/json", "x-api-key": key}
w3 = Web3(Web3.HTTPProvider("https://dev-api.blockvectra.network/v1/robinhood_mainnet", request_kwargs={"headers": headers}))
print(w3.eth.block_number)
```
```go
// Run with: go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
)
func main() {
ctx := context.Background()
c, err := rpc.DialOptions(ctx, "https://dev-api.blockvectra.network/v1/robinhood_mainnet", rpc.WithHeader("x-api-key", os.Getenv("BLOCKVECTRA_API_KEY")))
if err != nil {
log.Fatal(err)
}
number, err := ethclient.NewClient(c).BlockNumber(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Println(number)
}
```
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
reqwest = "0.13"
```
```rust
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::client::RpcClient;
use reqwest::header::{HeaderMap, HeaderValue};
#[tokio::main]
async fn main() -> Result<(), Box> {
let key = std::env::var("BLOCKVECTRA_API_KEY")?;
let mut headers = HeaderMap::new();
headers.insert("x-api-key", HeaderValue::from_str(&key)?);
let http_client = reqwest::Client::builder().default_headers(headers).build()?;
let rpc_client = RpcClient::new_http_with_client(http_client, "https://dev-api.blockvectra.network/v1/robinhood_mainnet".parse()?);
let provider = ProviderBuilder::new().connect_client(rpc_client);
println!("{}", provider.get_block_number().await?);
Ok(())
}
```
When you pass the key in a header, call `https://dev-api.blockvectra.network/v1/robinhood_mainnet` exactly as shown: the URL ends
with the chain name, **without** a trailing slash. JSON-RPC is served only at `/v1/{chain}`
and `/v1/{chain}/{api_key}`. A trailing slash (like `/v1/{chain}/`) or a request without a chain segment (like `/v1` or `/v1/`) returns `404` with an empty body.
The legacy `/v1/{api_key}` returns `404` with an `error.data.reason: "unknown_chain"` error.
An `Authorization: Bearer ` header also works and is used if `x-api-key` is absent
or empty. If both are present, a non-empty `x-api-key` wins.
### Batch calls [#batch-calls]
Send an array to make several calls in one request (up to 100 per batch). Note that each API key has a CU bucket (`cu_per_sec` refill, `burst_cu` capacity; shown per key in the console Keys table); a single request — including a whole JSON-RPC batch — whose total CU exceeds the key's burst capacity is rejected with `-32022 request_exceeds_burst`, even under the 100-calls-per-batch limit; split it into smaller batches. This example reads
the chain ID and an account balance in a single round trip:
```bash
curl -s "https://dev-api.blockvectra.network/v1/robinhood_mainnet" \
-H 'Content-Type: application/json' \
-H "x-api-key: $BLOCKVECTRA_API_KEY" \
-d '[
{"jsonrpc":"2.0","id":1,"method":"eth_chainId"},
{"jsonrpc":"2.0","id":2,"method":"eth_getBalance","params":["0x1111111111111111111111111111111111111111","latest"]}
]'
```
```ts
import { createPublicClient, http } from "viem";
const key = process.env.BLOCKVECTRA_API_KEY!;
const client = createPublicClient({
transport: http("https://dev-api.blockvectra.network/v1/robinhood_mainnet", {
batch: true,
fetchOptions: { headers: { "x-api-key": key } },
}),
});
// viem coalesces concurrent requests into a single JSON-RPC batch.
const [chainId, balance] = await Promise.all([
client.getChainId(),
client.getBalance({ address: "0x1111111111111111111111111111111111111111" }),
]);
console.log(chainId, balance);
// Run with: npx tsx example.mts
```
```python
import os
from web3 import Web3
key = os.environ["BLOCKVECTRA_API_KEY"]
headers = {"Content-Type": "application/json", "x-api-key": key}
w3 = Web3(Web3.HTTPProvider("https://dev-api.blockvectra.network/v1/robinhood_mainnet", request_kwargs={"headers": headers}))
with w3.batch_requests() as batch:
batch.add(w3.eth.chain_id)
batch.add(w3.eth.get_balance("0x1111111111111111111111111111111111111111"))
chain_id, balance = batch.execute()
print(chain_id, balance)
```
```go
// Run with: go mod init example && go get github.com/ethereum/go-ethereum && go mod tidy && go run .
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/ethereum/go-ethereum/rpc"
)
func main() {
ctx := context.Background()
c, err := rpc.DialOptions(ctx, "https://dev-api.blockvectra.network/v1/robinhood_mainnet", rpc.WithHeader("x-api-key", os.Getenv("BLOCKVECTRA_API_KEY")))
if err != nil {
log.Fatal(err)
}
var chainID, balance string
calls := []rpc.BatchElem{
{Method: "eth_chainId", Result: &chainID},
{Method: "eth_getBalance", Args: []any{"0x1111111111111111111111111111111111111111", "latest"}, Result: &balance},
}
if err := c.BatchCallContext(ctx, calls); err != nil {
log.Fatal(err)
}
for _, call := range calls {
if call.Error != nil {
log.Fatal(call.Error)
}
}
fmt.Println(chainID, balance)
}
```
```toml
[dependencies]
alloy = { version = "1", features = ["provider-http", "rpc-types"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
reqwest = "0.13"
```
```rust
use alloy::primitives::{Address, U256};
use alloy::rpc::client::RpcClient;
use reqwest::header::{HeaderMap, HeaderValue};
#[tokio::main]
async fn main() -> Result<(), Box> {
let key = std::env::var("BLOCKVECTRA_API_KEY")?;
let mut headers = HeaderMap::new();
headers.insert("x-api-key", HeaderValue::from_str(&key)?);
let http_client = reqwest::Client::builder().default_headers(headers).build()?;
let rpc_client = RpcClient::new_http_with_client(http_client, "https://dev-api.blockvectra.network/v1/robinhood_mainnet".parse()?);
let address: Address = "0x1111111111111111111111111111111111111111".parse()?;
let mut batch = rpc_client.new_batch();
let chain_id = batch.add_call::<_, U256>("eth_chainId", &())?;
let balance = batch.add_call::<_, U256>("eth_getBalance", &(address, "latest"))?;
batch.send().await?;
println!("{} {}", chain_id.await?, balance.await?);
Ok(())
}
```
Responses come back as an array, in the same order as the requests, matched by `id`.
If the gateway rejects the whole batch instead — insufficient balance, rate limiting, burst capacity, or more than 100 calls (see [Common errors](#common-errors) below) — it returns a single JSON-RPC error object instead of an array; viem's `batch: true` mode then surfaces this as an opaque `UnknownRpcError`, so retry a single call to see the actual error.
## 3. Understand CU billing [#3-understand-cu-billing]
Every billed call consumes **Compute Units (CU)**: cheap calls like `eth_blockNumber` or
`eth_chainId` cost 1 CU, common reads like `eth_getBlockByNumber` cost a handful, heavier
calls like `eth_call` or `eth_getLogs` cost more, and `debug_trace*` calls cost the most.
Usage is charged per account per hourly period, rounded down to whole billing units (1 unit = 1000 CU), with the remainder carrying over to the next period (so across periods the total charged is `floor(total CU / 1000)`); settlement runs about 15 minutes after the period ends. For example: 508 CU carried over + 2557 CU consumed = 3065 CU, resulting in 3 billing units charged and 65 CU carried over to the next period. See [Pricing](https://blockvectra.com/en/pricing/) for current prices.
The full method-by-method weight table and error codes live in
[API Reference → JSON-RPC](/en/api/json-rpc/) — this page only covers the
shape of a request.
### Common errors [#common-errors]
| What you did | What comes back | Action |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Unknown or not yet public chain, or legacy `/v1/{api_key}` | HTTP `404` with JSON body `error.data.reason: "unknown_chain"` | Check the chain name in the URL |
| Request without a chain segment (e.g. `/v1` or `/v1/`), or API key missing, unknown or disabled | HTTP `404` with an empty body | Include the chain name in the URL (`/v1/{chain}`), or use a valid, active API key (brand-new or rotated keys take a few seconds to take effect; wait a moment and retry) |
| Balance is zero or negative | HTTP `402`, JSON-RPC code `-32020` | Top up your balance or wait for the free refill |
| Sent requests too quickly (rate limit or temporary overload) | HTTP `429` (or `200`), JSON-RPC code `-32005` | Retry later (honour `Retry-After` when present) |
| Single request or batch exceeds key burst capacity | HTTP `429`, JSON-RPC code `-32022` (`request_exceeds_burst`) | Split the request into smaller batches (can never succeed as sent, even under the 100-call limit) |
| Upstream node is temporarily unavailable | HTTP `200`, JSON-RPC code `-32603` (`upstream unavailable`), not billed | Retry the request |
| Historical state outside this chain's state window (Ethereum: about the last 250,000 blocks) | HTTP `200`, JSON-RPC code `-32011`, not billed | Query a more recent block |
| Transaction or block not found, or response too large; on Ethereum, block / receipt / log queries outside the recent window also return -32000 "old data not available due to pruning" (not billed; see [Supported Chains → Ethereum](/en/chains/#ethereum)) | HTTP `200`, JSON-RPC code `-32000` | Change the request (verify hash or block number; malformed trace hashes return transaction not found) |
| Tracer not allowed, or trace timeout not allowed (`debug_trace*`) | HTTP `200`, JSON-RPC code `-32602`, not billed | Use an allowed native tracer (`callTracer`, `flatCallTracer`, `prestateTracer`, `4byteTracer`, `noopTracer`, or omit) and timeout ≤ 30s |
| A method the chain's method list does not allow (see [Supported Chains](/en/chains/)) | HTTP `200`, JSON-RPC code `-32601`, not billed | Call only methods the chain allows |
| Malformed JSON body | HTTP `200`, JSON-RPC code `-32700`, not billed | Fix request JSON syntax |
| More than 100 calls in one batch | HTTP `200`, JSON-RPC code `-32600` (`batch too large`), not billed | Split the batch into at most 100 calls |
The rejections above are never billed. Every accepted call that gets a response is charged at the method's published CU weight; the error-code table lists the cases that are not billed (see the Billed column in [Error Codes](/en/api/json-rpc/#error-codes)). For `debug_trace*` calls, tracers must be built-in native tracers (`callTracer`, `flatCallTracer`, `prestateTracer`, `4byteTracer`, `noopTracer`, or omitted) and timeouts must be ≤ 30s (`-32602`).
## 4. Call the Data API [#4-call-the-data-api]
The Data API exposes read-only chain data (blocks, transactions, balances, holders, DEX
activity, and more) as REST/JSON. Every route except `GET https://dev-api.blockvectra.network/v1/data/chains` is prefixed
with a chain identifier: `robinhood_mainnet` is the chain identifier (the `chain` field returned by `/chains` and in `meta`) used in every path below.
`GET https://dev-api.blockvectra.network/v1/data/chains` lists only public chains and returns only `{"data": [...]}` (no
`meta`, no `next_cursor`). Requests are metered and billed in Compute Units (CU); only 2xx successful responses are billed.
Every request requires the same API key as JSON-RPC — pass it in the `x-api-key` header.
Every chain-scoped success response uses the same envelope:
`data` (the payload), `next_cursor` (an opaque string, only present when there's another page — otherwise the
key is absent entirely, never `null`), and `meta` (`chain`, `chain_slug` (upper-case form of `chain`), `chain_external_id`,
`as_of_block`, `finalized_block`, `coverage`, `refreshed_at`). Error responses usually contain `{"error":{"code","message"}}` —
`409 not_indexed_yet` adds `indexed_through` (the highest indexed block). There are two exceptions for `404` errors: an unknown or not-public chain returns HTTP `404` with `error.code` `not_found` (decided before the key check, not billed, and not rate-limited; chain names must be exact lowercase slugs), while a missing, unknown, or disabled API key gets
HTTP `404` with an empty body, as on JSON-RPC. Rate-limited requests return HTTP `429` (`error.code` `rate_limited`, `data.reason: "key_rate_limit"`), and exhausted balance returns HTTP `402` (`error.code` `insufficient_balance`); both are not billed. Values that can exceed 2^53 (balances, token amounts) are decimal
strings, never JSON numbers.
**Look up a block by number:**
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/blocks/72838701" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const res = await fetch("https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/blocks/72838701", {
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const body = await res.json();
console.log(body);
// Run with: npx tsx example.mts
```
```python
import os, requests
res = requests.get(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/blocks/72838701",
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
print(res.json())
```
```json
{
"data": {
"number": 72838701,
"hash": "0x9f2c1e7a4b6d3f805e1c9a72b4d6f1e0a3c8b5d7e2f4a1c6b9d3e7f0a2c4b6d8",
"parent_hash": "0x1a3c5e7f9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e7b9d1f3a",
"timestamp": "2026-09-26T05:41:07Z",
"miner": "0x00000000000000000000000000000000000a4b05",
"gas_limit": 32000000,
"gas_used": 4821932,
"base_fee_per_gas": "100000000",
"state_root": "0x2b4d6f8a1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d",
"transactions_root": "0x3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5e",
"receipts_root": "0x4d6f8a1c3e5b7d9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f",
"tx_count": 239,
"size": 48213,
"l1_block_number": null,
"extra": {}
},
"meta": {
"chain": "robinhood_mainnet",
"chain_slug": "ROBINHOOD_MAINNET",
"chain_external_id": "eip155:4663",
"as_of_block": 72838957,
"finalized_block": 72838701,
"coverage": "full",
"refreshed_at": "2026-09-27T02:15:03Z"
}
}
```
A number above the indexed head (`as_of_block`) is `409` (`error.code: "not_indexed_yet"`)
with `indexed_through` telling you the highest indexed block — the data isn't there yet, so
retry later. A number at or below `as_of_block` but above `finalized_block` is `409`
(`error.code: "finality_exceeded"`). A number at or below `finalized_block` with no live row
(never indexed, or rolled back by a reorg) is `404` (`error.code: "not_found"`) — finality margin varies by chain; see [Supported Chains](/en/chains/).
**Check data freshness** (how far each tracked dataset lags behind the chain head — useful
for a status page or a pre-flight check before you trust a query):
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/status/freshness" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const res = await fetch("https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/status/freshness", {
headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
});
const body = await res.json();
console.log(body);
// Run with: npx tsx example.mts
```
```python
import os, requests
res = requests.get(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/status/freshness",
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
print(res.json())
```
```json
{
"data": [
{
"dataset": "blocks",
"category": "raw",
"max_block_number": 72838957,
"max_day": null,
"max_time": "2026-09-27T02:15:01Z",
"seconds_behind": 0,
"blocks_behind": 0,
"days_behind": null,
"checked_at": "2026-09-27T02:15:07Z"
}
],
"meta": {
"chain": "robinhood_mainnet",
"chain_slug": "ROBINHOOD_MAINNET",
"chain_external_id": "eip155:4663",
"as_of_block": 72838957,
"finalized_block": 72838701,
"coverage": "full",
"refreshed_at": "2026-09-27T02:15:07Z"
}
}
```
*(Abridged: the response has one row per dataset; only the `blocks` row is shown. The `traces` row also carries `coverage_from_block`, `coverage_to_block` and `coverage_complete`.)*
If freshness data is temporarily unavailable for this chain, this returns `503` (`error.code: "unavailable"`) instead of a partial result; the response carries a `Retry-After` header (seconds) — wait at least that long, then retry.
**List an address's ERC-20 balances** (a snapshot refreshed every 6 hours, filtered to
non-zero balances, ordered by token):
```bash
curl -s "https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"
```
```ts
const res = await fetch(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances",
{ headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! } },
);
const body = await res.json();
console.log(body);
// Run with: npx tsx example.mts
```
```python
import os, requests
res = requests.get(
"https://dev-api.blockvectra.network/v1/data/robinhood_mainnet/addresses/0x1111111111111111111111111111111111111111/balances",
headers={"x-api-key": os.environ["BLOCKVECTRA_API_KEY"]},
)
print(res.json())
```
```json
{
"data": [
{ "token": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", "balance": "500000000", "symbol": "WBTC", "decimals": 8 },
{ "token": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "balance": "1250000000", "symbol": "USDC", "decimals": 6 }
],
"meta": {
"chain": "robinhood_mainnet",
"chain_slug": "ROBINHOOD_MAINNET",
"chain_external_id": "eip155:4663",
"as_of_block": 72838957,
"finalized_block": 72838701,
"coverage": "full",
"refreshed_at": "2026-09-27T02:10:00Z"
}
}
```
An address with no non-zero balances still returns `200` with `data: []` — never `404`.
Pass `?limit=` (default 50, max 500) and the returned `next_cursor` to page through more.
Full endpoint coverage — blocks, transactions, addresses, tokens, NFTs, DEX, tokenized
stocks — is available in [API Reference → Data API](/en/api/data/).
## Where to next [#where-to-next]
* [API Reference → JSON-RPC](/en/api/json-rpc/) — methods, CU weights, error codes
* [API Reference → Data API](/en/api/data/) — REST endpoints for chain data
* [Datasets](/en/datasets/) — derived datasets across supported chains
* [Supported Chains](/en/chains/) — network identifiers and endpoint URLs