Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: BlockVectra Data DEX API
version: 0.1.3
description: BlockVectra Data API — read-only REST endpoints over indexed data for each supported chain. The base URL is `https://api.blockvectra.com/v1/data`; supply your API key in the `x-api-key` header.
contact:
name: BlockVectra
servers:
- url: https://api.blockvectra.com/v1/data
description: BlockVectra Data API
security:
- ApiKeyHeader: []
tags:
- name: DEX
description: DEX swaps and daily prices.
paths:
/{chain}/dex/swaps:
get:
tags:
- DEX
summary: List DEX swaps by pool or token
description: 'Keyset-paginated list of DEX swaps, queried by pool or by token contract. Exactly one of `pool` or `token` must be specified. For `token`, a block window is mandatory (`from_block` and `to_block` are both required, spanning at most 100,000 blocks). The parameter `trader` is not supported and will return `400 bad_request`. The window''s lower bound is computed for the swap dataset, not from the chain''s start alone: it is above `GET /chains` `coverage.from_block` only when the swap dataset has a recorded build coverage starting higher (a chain whose first swap comes late is not a gap). A window entirely before it is `422 no_coverage`; one that starts before it, or has no `from_block`, is served as far as it goes with `meta.coverage = "partial"`. Limitation: during an *ascending* backfill the hole between the backfilled tail and the live start is not detected (the dataset''s start is its lowest covered block), so a window inside that hole is still `200` with an empty list.'
operationId: getDexSwaps
parameters:
- $ref: '#/components/parameters/ChainParam'
- name: pool
in: query
required: false
description: Exactly one of `pool`/`token` is required. Accepts either a 20-byte pool address (left-zero-padded to 32 bytes, Uniswap V2/V3 convention) or a 32-byte pool key (Uniswap V4 `poolId`); `0x` optional, either case.
schema:
type: string
- name: token
in: query
required: false
description: Exactly one of `pool`/`token` is required. 20-byte token address, `0x` optional, either case.
schema:
$ref: '#/components/schemas/AddressInput'
- name: trader
in: query
required: false
description: The 'trader' parameter is not supported; filter by 'pool' or 'token' instead.
schema:
type: string
- name: from_block
in: query
required: false
description: Lower bound (inclusive). Optional for `pool`; **required** for `token`.
schema:
type: integer
minimum: 0
- name: to_block
in: query
required: false
description: Upper bound (inclusive). Optional for `pool` (defaults to `as_of_block`, the newest fully written block); **required** for `token`. An explicit value above `as_of_block` is `409 not_indexed_yet`. `meta.finalized_block` is a marker and never bounds the window.
schema:
type: integer
minimum: 0
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Cursor'
responses:
'200':
description: Matching swaps, newest first.
content:
application/json:
schema:
$ref: '#/components/schemas/DexSwapListEnvelope'
'400':
description: '`error.code = "bad_request"`: `trader` present; neither or both of `pool`/`token` given; malformed `pool`/`token`; `token` filter missing `from_block`/`to_block`; `from_block > to_block`; invalid `limit`; or an invalid `cursor`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
example:
error:
code: bad_request
message: the 'trader' parameter is not supported; filter by 'pool' or 'token' instead
'401':
$ref: '#/components/responses/GatewayUnauthorized'
'402':
$ref: '#/components/responses/GatewayPaymentRequired'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: 'One of three codes: `error.code = "not_indexed_yet"` (window above `as_of_block`); `error.code = "window_too_large"` (`token` dimension window spans more than 100,000 blocks); or `error.code = "too_many_pools"` (`token` matches more than 200 pools — retry with the `pool` dimension instead). On a chain with no indexed block at all it is `error.code = "not_indexed_yet"` without `indexed_through` (see "Errors").'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
examples:
too_many_pools:
value:
error:
code: too_many_pools
message: token matches 214 pools, exceeding limit of 200; query by pool dimension instead
'422':
$ref: '#/components/responses/NoCoverage'
'429':
$ref: '#/components/responses/GatewayRateLimited'
'503':
$ref: '#/components/responses/Unavailable'
/{chain}/dex/prices:
get:
tags:
- DEX
summary: Daily DEX token prices
description: 'Daily volume-weighted average price (VWAP) and volume for a DEX token over a specified date range (up to 90 days). Not paginated: `next_cursor` is never present.
**Snapshot freshness**: prices come from a daily table that the data store refreshes once a day, so the latest day can be missing or partial for up to about a day. `as_of_block` here is the service''s current indexed head, not the block height of the snapshot; read the snapshot time from `meta.refreshed_at`, and treat the data as stale when `refreshed_at` is `null`.'
operationId: getDexPrices
parameters:
- $ref: '#/components/parameters/ChainParam'
- name: token
in: query
required: true
description: 20-byte base token address, `0x` optional, either case.
schema:
$ref: '#/components/schemas/AddressInput'
- name: quote
in: query
required: false
description: Optional 20-byte quote token address to restrict to a single base/quote pair.
schema:
$ref: '#/components/schemas/AddressInput'
- name: from
in: query
required: true
description: UTC start date, inclusive, `YYYY-MM-DD`.
schema:
type: string
format: date
- name: to
in: query
required: true
description: UTC end date, inclusive, `YYYY-MM-DD`. `to - from` must be `<= 90` days.
schema:
type: string
format: date
responses:
'200':
description: Daily price/volume rows for `[from, to]` (empty array if none).
content:
application/json:
schema:
$ref: '#/components/schemas/DexPriceListEnvelope'
'400':
description: '`error.code = "bad_request"`: missing `token`/`from`/`to`; invalid `token`/`quote`; `from`/`to` not a valid `YYYY-MM-DD` calendar date; or `from` after `to`.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
'401':
$ref: '#/components/responses/GatewayUnauthorized'
'402':
$ref: '#/components/responses/GatewayPaymentRequired'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: '`error.code = "span_exceeded"`: `to - from` is more than 90 days.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
example:
error:
code: span_exceeded
message: date span from 2026-01-01 to 2026-12-31 is 364 days, exceeding the 90-day limit
'422':
$ref: '#/components/responses/NoCoverage'
'429':
$ref: '#/components/responses/GatewayRateLimited'
'503':
$ref: '#/components/responses/Unavailable'
components:
schemas:
AddressInput:
type: string
description: 20-byte address. `0x`/`0X` prefix optional; either hex digit case is accepted.
pattern: ^(0[xX])?[0-9a-fA-F]{40}$
example: '0xAbCdEf0123456789aBcDeF0123456789aBCDEF01'
DexPriceListEnvelope:
type: object
required:
- data
- meta
description: '`next_cursor` is never present (this endpoint has no pagination).'
properties:
data:
type: array
items:
$ref: '#/components/schemas/DexDailyPrice'
meta:
$ref: '#/components/schemas/Meta'
DecimalString:
type: string
description: Human-adjusted (decimals-scaled) fixed-point value rendered as a plain decimal string — never a JSON number.
pattern: ^-?[0-9]+(\.[0-9]+)?$
Meta:
type: object
description: Metadata present on every success response.
required:
- chain
- chain_slug
- chain_external_id
- as_of_block
- safe_block
- finalized_block
- coverage
- refreshed_at
properties:
chain:
type: string
description: Chain identifier; the value of the `{chain}` path parameter.
example: robinhood_mainnet
chain_slug:
type: string
description: Canonical uppercase chain slug; `chain` is always its lowercase form.
example: ROBINHOOD_MAINNET
chain_external_id:
type: string
description: CAIP-2 formatted external chain identifier (`eip155:{chain_id}`).
example: eip155:4663
as_of_block:
type: integer
format: int64
description: 'The newest fully written block (the `blocks` table''s maximum height); the only serving bound for block-scoped endpoints. For snapshot endpoints (balances, holders, nfts, dex prices, stocks) it is the service''s current indexed head, not the block height of the snapshot: read the snapshot time from `meta.refreshed_at`, and treat the data as stale when `refreshed_at` is `null`.'
safe_block:
type:
- integer
- 'null'
format: int64
description: 'A marker only: the node''s consensus `safe` block. Always present; `null` while no value is known (the node has no such tag, or the indexer has not read one yet). Only grows, never below `finalized_block`, and may be above or below `as_of_block`. It never truncates, rejects or bounds a response; see Finality in the API description.'
finalized_block:
type:
- integer
- 'null'
format: int64
description: 'A marker only: the node''s consensus `finalized` block. Always present; `null` while no value is known (the node has no such tag, or the indexer has not read one yet). Only grows, and may be above or below `as_of_block`. It never truncates, rejects or bounds a response; see Finality in the API description.'
coverage:
type: string
enum:
- full
- partial
description: '`"partial"` appears only from `GET /{chain}/addresses/{address}/transactions` and `GET /{chain}/addresses/{address}/transfers`, when `clamp=true` narrowed the served window, and from those two, `GET /{chain}/tokens/{token}/transfers` and `GET /{chain}/dex/swaps` when the block window starts before the lower bound of the dataset the endpoint reads -- the chain''s first indexed block, or higher while that dataset is still being backfilled (see Coverage in the API description and each endpoint''s window description). Every other endpoint reports `"full"`.'
refreshed_at:
description: 'When the data behind this response was last updated (UTC): the timestamp of `as_of_block` for block-based endpoints, the latest snapshot time for snapshot endpoints, and the last update time for token metadata and status. `null` means the snapshot''s update time is currently unknown: treat the data as stale; it never means "just refreshed".'
oneOf:
- $ref: '#/components/schemas/Timestamp'
- type: 'null'
ErrorBody:
type: object
description: The only shape any non-2xx response ever takes. `code` and `message` are always present; `indexed_through` is present only on `error.code = "not_indexed_yet"` on a chain with an indexed block, and absent (never `null`) otherwise. No other field is ever added.
required:
- error
additionalProperties: false
properties:
error:
type: object
required:
- code
- message
additionalProperties: false
properties:
code:
type: string
description: Stable machine-readable identifier; see each operation for the exact set of values it can return.
message:
type: string
description: Free-form English text for logs/debugging. Do not pattern-match on it.
indexed_through:
type: integer
format: int64
description: 'Highest indexed block on this chain. Present only when `code = "not_indexed_yet"` and the chain has at least one indexed block; absent when it has none at all (an absent value is not `0`: block 0 indexed is `indexed_through: 0`).'
data:
type: object
description: Optional structured error details.
additionalProperties: true
Timestamp:
type: string
format: date-time
description: '`YYYY-MM-DDTHH:MM:SSZ`, UTC, second precision.'
example: 2026-09-26 05:41:07+00:00
DexSwapListEnvelope:
type: object
required:
- data
- meta
description: '`next_cursor` is present only when the page returned exactly `limit` rows.'
properties:
data:
type: array
items:
$ref: '#/components/schemas/DexSwap'
next_cursor:
type: string
description: Opaque pagination token from the previous response's `next_cursor`; pass it back unchanged.
meta:
$ref: '#/components/schemas/Meta'
Address:
type: string
description: 20-byte address, canonical form always `0x` + 40 lowercase hex digits.
pattern: ^0x[0-9a-f]{40}$
DateOnly:
type: string
format: date
description: '`YYYY-MM-DD`, UTC calendar day.'
example: 2026-09-26
DexDailyPrice:
type: object
required:
- day
- token
- token_symbol
- token_name
- quote_token
- quote_symbol
- quote_name
- base_decimals
- quote_decimals
- swap_count
- base_volume_raw
- quote_volume_raw
- base_volume
- quote_volume
- vwap
- first_price
- last_price
- min_price
- max_price
- first_price_numerator
- first_price_denominator
- last_price_numerator
- last_price_denominator
- min_price_numerator
- min_price_denominator
- max_price_numerator
- max_price_denominator
- refreshed_at
properties:
day:
$ref: '#/components/schemas/DateOnly'
token:
$ref: '#/components/schemas/Address'
token_symbol:
oneOf:
- type: string
- type: 'null'
token_name:
oneOf:
- type: string
- type: 'null'
quote_token:
$ref: '#/components/schemas/Address'
description: The all-zero address represents native ETH as the quote asset.
quote_symbol:
oneOf:
- type: string
- type: 'null'
description: '`"ETH"` when `quote_token` is the zero address.'
quote_name:
oneOf:
- type: string
- type: 'null'
description: '`"Ether"` when `quote_token` is the zero address.'
base_decimals:
oneOf:
- type: integer
minimum: 0
maximum: 255
- type: 'null'
quote_decimals:
oneOf:
- type: integer
minimum: 0
maximum: 255
- type: 'null'
description: '`18` when `quote_token` is the zero address.'
swap_count:
type: integer
format: int64
base_volume_raw:
$ref: '#/components/schemas/UInt256String'
quote_volume_raw:
$ref: '#/components/schemas/UInt256String'
base_volume:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
description: '`base_volume_raw` scaled by `base_decimals`; `null` when `base_decimals` is unknown.'
quote_volume:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
vwap:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
first_price:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
last_price:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
min_price:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
max_price:
oneOf:
- $ref: '#/components/schemas/DecimalString'
- type: 'null'
first_price_numerator:
$ref: '#/components/schemas/UInt256String'
first_price_denominator:
$ref: '#/components/schemas/UInt256String'
last_price_numerator:
$ref: '#/components/schemas/UInt256String'
last_price_denominator:
$ref: '#/components/schemas/UInt256String'
min_price_numerator:
$ref: '#/components/schemas/UInt256String'
min_price_denominator:
$ref: '#/components/schemas/UInt256String'
max_price_numerator:
$ref: '#/components/schemas/UInt256String'
max_price_denominator:
$ref: '#/components/schemas/UInt256String'
refreshed_at:
$ref: '#/components/schemas/Timestamp'
DexSwap:
type: object
required:
- protocol
- pool_key
- amount0
- amount1
- sqrt_price_x96
- liquidity
- tick
- lp_fee
- sender
- recipient
- block_number
- block_timestamp
- tx_hash
- tx_index
- log_index
properties:
protocol:
type: string
description: e.g. "uniswap_v3".
pool_key:
$ref: '#/components/schemas/Hash32'
description: 32-byte key; a V2/V3 pool address is left-zero-padded into this shape.
amount0:
$ref: '#/components/schemas/Int256String'
amount1:
$ref: '#/components/schemas/Int256String'
sqrt_price_x96:
$ref: '#/components/schemas/UInt256String'
liquidity:
$ref: '#/components/schemas/UInt256String'
tick:
type: integer
format: int32
lp_fee:
oneOf:
- type: integer
- type: 'null'
sender:
$ref: '#/components/schemas/Address'
recipient:
oneOf:
- $ref: '#/components/schemas/Address'
- type: 'null'
block_number:
type: integer
format: int64
block_timestamp:
$ref: '#/components/schemas/Timestamp'
tx_hash:
$ref: '#/components/schemas/Hash32'
tx_index:
type: integer
log_index:
type: integer
Hash32:
type: string
description: 32-byte hash/topic, canonical form always `0x` + 64 lowercase hex digits.
pattern: ^0x[0-9a-f]{64}$
Int256String:
type: string
description: Signed integer that may exceed +-2^53, rendered as a plain decimal string, optionally sign-prefixed.
pattern: ^-?[0-9]+$
UInt256String:
type: string
description: Non-negative integer that may exceed 2^53, rendered as a plain decimal string — never a JSON number, never scientific or hex notation.
pattern: ^[0-9]+$
example: '9007199254740993'
responses:
GatewayPaymentRequired:
description: 'Payment required: the paid balance or the free grant is exhausted. The response body is `{"error":{"code":"insufficient_balance"}}`. Rejected requests are not billed.'
Unavailable:
description: '`error.code = "unavailable"`: the service is temporarily unavailable, timed out, busy on this chain (no query slot freed up in time, see `limits.max_concurrent_queries` in `GET /chains`), or shutting down (see "Errors" above). Always carries an HTTP `Retry-After` header (seconds); wait at least that long before retrying.'
headers:
Retry-After:
required: true
schema:
type: integer
minimum: 1
description: Seconds to wait before retrying.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
example:
error:
code: unavailable
message: service temporarily unavailable, please retry
GatewayRateLimited:
description: Rate limited. `{"error":{"code":"rate_limited"}}` when the key CU rate limit is exceeded (the response includes `Retry-After`) or the account call rate limit is exceeded (no `Retry-After`). `{"error":{"code":"cost_exceeds_burst"}}` when a single request exceeds the key burst capacity; split the request, retrying as sent never succeeds. Rejected requests are not billed.
NotFound:
description: '`error.code = "not_found"`: the target object was not found, or `{chain}` is not a registered or public chain (returns HTTP 404 with `error.code = "not_found"`, not billed).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
examples:
unknown_chain:
summary: Unknown or not-public chain
value:
error:
code: not_found
message: unknown chain
object_not_found:
summary: Resource not found
value:
error:
code: not_found
message: block not found
example:
error:
code: not_found
message: unknown chain
NoCoverage:
description: '`error.code = "no_coverage"`: no data here, which is permanent except while a dataset is being backfilled (a windowed endpoint''s lower bound can move down as the backfill proceeds, so retry later). Either the chain does not have this endpoint''s capability, or the requested block (or whole block window) is before the chain''s first covered block (`coverage.from_block`) -- for the windowed endpoints, the lower bound of the dataset they read, which is that block or higher while the dataset is still being backfilled -- or the capability needs history from genesis (`balances`, `holders`, `nfts`) and `coverage.from_block` is not `0` yet or the indexed blocks have holes, or (traces) the block falls in a permanent trace coverage gap. A block window entirely before the lower bound is reported as this `422` even when its `to_block` is also above `as_of_block`. See Coverage in the API description (the history start in examples is a placeholder; read `GET /chains` `coverage.from_block` at runtime).'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorBody'
examples:
capability:
value:
error:
code: no_coverage
message: chain 'eth_mainnet' does not support balances
before_history:
summary: the start is live -- read `GET /chains` `coverage.from_block` at runtime
value:
error:
code: no_coverage
message: block 1 is before indexed history start <from_block> on chain 'eth_mainnet'
history_not_at_genesis:
summary: balances, holders and nfts need history from genesis
value:
error:
code: no_coverage
message: 'chain ''eth_mainnet'' does not serve balances yet: it needs history from genesis and indexed history starts at block <from_block>'
GatewayUnauthorized:
description: 'HTTP 401: a missing API key returns missing_api_key; an unknown, disabled, or revoked key returns the same invalid_api_key error envelope. error.code and error.data.reason contain the reason. Rejected requests are not billed.'
content:
application/json:
examples:
missing_api_key:
value:
error:
code: missing_api_key
message: 'missing API key: send it in the x-api-key header'
data:
reason: missing_api_key
docs_url: https://docs.blockvectra.com/en/errors/#missing_api_key
retryable: false
invalid_api_key:
value:
error:
code: invalid_api_key
message: 'invalid API key: it is unknown, disabled or revoked; check the key in the console'
data:
reason: invalid_api_key
docs_url: https://docs.blockvectra.com/en/errors/#invalid_api_key
retryable: false
parameters:
ChainParam:
name: chain
in: path
required: true
description: 'Chain identifier: the `chain` value of an entry in `GET /chains` (for example `robinhood_mainnet`), which lists every value this API accepts, i.e. the exact lowercase form of that entry''s `chain_slug`. Matching is exact and case-sensitive; other spellings, aliases and numeric chain IDs are not accepted and return `404 not_found` (unknown or not-public chain). What the chain serves differs per chain: see that entry''s `features` and `coverage`.'
schema:
type: string
pattern: ^[a-z][a-z0-9]*(_[a-z0-9]+)+$
examples:
robinhood_mainnet:
value: robinhood_mainnet
eth_mainnet:
value: eth_mainnet
Cursor:
name: cursor
in: query
required: false
description: Opaque pagination token from the previous response's `next_cursor`; pass it back unchanged.
schema:
type: string
Limit:
name: limit
in: query
required: false
description: Page size. Absent defaults to 50. `0` or a non-integer value is `400 bad_request`. A value above 500 is clamped to 500, not rejected.
schema:
type: integer
minimum: 1
default: 50
maximum: 500
securitySchemes:
ApiKeyHeader:
type: apiKey
in: header
name: x-api-key
description: Your BlockVectra API key.