DEX daily prices API: OHLC and VWAP for any token
Get daily DEX OHLC prices and VWAP for a token with one GET /{chain}/dex/prices request, plus exact fractions in TypeScript and Python and backfills.
GET /{chain}/dex/prices returns daily DEX open, high, low, close and VWAP prices for one token for a date range. Developers and AI agents call it with the same API key.
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/dex/prices?token=0x1Cdad396DB64BDa184d5182A97Dd9B3C62100b7D&from=2026-09-01&to=2026-09-30" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"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 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).
Request parameters and limits
The endpoint route is GET https://api.blockvectra.com/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 |
Constraints and error codes
When a request violates constraints, the API returns a structured error body {"error":{"code","message"}}:
- HTTP 400 (
bad_request): Missing required query parameters (token,from, orto), invalidtoken/quoteaddress syntax, invalidYYYY-MM-DDcalendar dates, orfromis afterto. - HTTP 409 (
span_exceeded):to - fromis more than 90 days. - HTTP 404 (
error.code: not_found,error.data.reason: unknown_chain):{chain}is unknown or not public inGET /chains; chain lookup precedes query validation. - HTTP 422 (
no_coverage): The chain does not support thedex_pricesdataset capability. - HTTP 503 (
unavailable): Service temporarily unavailable; retry according to theRetry-Afterheader.
Request examples
The following examples query daily DEX prices for a base token across September 2026:
curl -s "https://api.blockvectra.com/v1/data/robinhood_mainnet/dex/prices?token=0x1Cdad396DB64BDa184d5182A97Dd9B3C62100b7D&from=2026-09-01&to=2026-09-30" \
-H "x-api-key: $BLOCKVECTRA_API_KEY"Detailed field reference
Each entry in data represents aggregated daily DEX metrics for the token pair on that UTC date:
Token and quote assets
day(string): UTC date inYYYY-MM-DDformat.token(string): 20-byte base token address in lowercase0x-prefixed hex.token_symbol(string ornull): Base token symbol.token_name(string ornull): 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 ornull): Quote asset symbol ("ETH"whenquote_tokenis the all-zero address).quote_name(string ornull): Quote asset display name ("Ether"whenquote_tokenis the all-zero address).base_decimals(integer ornull): Base token decimals (0–255).quote_decimals(integer ornull): Quote asset decimals (18whenquote_tokenis the all-zero address).
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 ornull): Human-readable base token volume scaled bybase_decimalsas aDecimalString;nullwhenbase_decimalsis unknown.quote_volume(string ornull): Quote volume scaled by quote decimals as aDecimalString, ornull.
Price indicators and VWAP
vwap(string ornull): Volume-weighted average price as aDecimalString, ornull.first_price(string ornull): First price indicator as aDecimalString, ornull.last_price(string ornull): Last price indicator as aDecimalString, ornull.min_price(string ornull): Minimum price indicator as aDecimalString, ornull.max_price(string ornull): Maximum price indicator as aDecimalString, ornull.
Exact fraction fields
first_price_numerator/first_price_denominator(string): Raw integer amounts of the swap behindfirst_price(UInt256String): the numerator isquote_amount_rawin quote atomic units and the denominator isbase_amount_rawin base atomic units. They are not the scaled human-readablefirst_priceas an exact fraction.last_price_numerator/last_price_denominator(string): Raw integer amounts of the swap behindlast_price(UInt256String): the numerator isquote_amount_rawin quote atomic units and the denominator isbase_amount_rawin base atomic units. They are not the scaled human-readablelast_priceas an exact fraction.min_price_numerator/min_price_denominator(string): Raw integer amounts of the swap behindmin_price(UInt256String): the numerator isquote_amount_rawin quote atomic units and the denominator isbase_amount_rawin base atomic units. They are not the scaled human-readablemin_priceas an exact fraction.max_price_numerator/max_price_denominator(string): Raw integer amounts of the swap behindmax_price(UInt256String): the numerator isquote_amount_rawin quote atomic units and the denominator isbase_amount_rawin base atomic units. They are not the scaled human-readablemax_priceas an exact fraction.refreshed_at(string): Refresh timestamp for this row (ISO-8601 UTC timestamp).
The raw ratio numerator / denominator is quote atomic units per base atomic unit. The human-readable price (quote tokens per base token) is the raw ratio × 10^(base_decimals − quote_decimals). When base_decimals or quote_decimals is null, the human-readable price is unknown; do not assume 18. 0 is a valid decimals value. When a denominator is 0, do not divide; treat the human-readable price as null. For the same pair (same decimals), price direction and relative change can be computed directly from the raw ratios.
Envelope metadata (meta)
chain: Chain identifier.chain_slug: Canonical uppercase chain slug.chain_external_id: CAIP-2 formatted chain identifier.as_of_block: The chain's newest fully written block (reported by this dataset, not checked against request parameters).coverage: Coverage classification (reports"full"for this endpoint).refreshed_at: Metadata refreshed timestamp. May benull:nullmeans the update time of this data is unknown and it should be treated as stale; block-based endpoints always return a value.
Why prices use exact numerators and denominators
Standard JSON numbers rely on IEEE-754 double-precision floats, which present precision limitations:
- Floating-point truncation and drift: Float64 values provide only 53 bits of precision, and dividing token quantities yields rounding drift that compounds across calculations.
- 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.
Handling exact fractions in TypeScript (BigInt)
In TypeScript, native BigInt handles ratio comparison and decimals scaling without floating-point conversion. isPriceUp compares the raw ratios of one pair; rawRatioToHumanString applies the decimals scaling and returns null when decimals are unknown or the denominator is 0. Its output is truncated (not rounded) to displayDigits digits: this is display truncation, separate from the token's own precision, and does not claim an exact finite decimal expansion.
interface DexDailyPrice {
base_decimals: number | null;
quote_decimals: number | null;
// Raw amounts: numerator is quote_amount_raw, denominator is base_amount_raw.
first_price_numerator: string;
first_price_denominator: string;
last_price_numerator: string;
last_price_denominator: string;
}
// 1. Raw ratios of one pair share the same decimals, so they compare directly.
// a / b > c / d is equivalent to a * d > c * b (denominators must be non-zero)
export function isPriceUp(row: DexDailyPrice): boolean | null {
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);
if (openDen === 0n || closeDen === 0n) return null;
return closeNum * openDen > openNum * closeDen;
}
// 2. Raw ratio -> human-readable price (quote tokens per base token):
// human = numerator / denominator * 10^(baseDecimals - quoteDecimals)
// Returns null when a decimals value is null (0 is valid) or the denominator is 0.
// The result is truncated to displayDigits digits for display only.
export function rawRatioToHumanString(
numeratorStr: string,
denominatorStr: string,
baseDecimals: number | null,
quoteDecimals: number | null,
displayDigits = 18
): string | null {
if (baseDecimals === null || quoteDecimals === null) return null;
let num = BigInt(numeratorStr);
let den = BigInt(denominatorStr);
if (den === 0n) return null;
const shift = baseDecimals - quoteDecimals;
if (shift >= 0) {
num *= 10n ** BigInt(shift);
} else {
den *= 10n ** BigInt(-shift);
}
const unit = 10n ** BigInt(displayDigits);
const scaled = (num * unit) / den;
const intPart = scaled / unit;
if (displayDigits === 0) return intPart.toString();
const fracPart = (scaled % unit).toString().padStart(displayDigits, "0");
return `${intPart}.${fracPart}`;
}Handling exact fractions in Python
Python's standard library provides fractions.Fraction for exact rational arithmetic. human_price applies the decimals scaling exactly and returns None when decimals are unknown or the denominator is 0; format_fraction rounds half to even with integer arithmetic, only for display.
from fractions import Fraction
def raw_ratio(numerator, denominator):
"""Raw ratio: quote atomic units per base atomic unit. None when the denominator is 0."""
den = int(denominator)
if den == 0:
return None
return Fraction(int(numerator), den)
def human_price(numerator, denominator, base_decimals, quote_decimals):
"""Quote tokens per base token = raw ratio * 10^(base_decimals - quote_decimals).
Returns None when a decimals value is None (0 is valid) or the denominator is 0.
"""
if base_decimals is None or quote_decimals is None:
return None
raw = raw_ratio(numerator, denominator)
if raw is None:
return None
return raw * Fraction(10) ** (base_decimals - quote_decimals)
def percentage_change(row):
"""Same pair, same decimals: raw ratios compare directly. Exact Fraction, no float."""
open_raw = raw_ratio(row["first_price_numerator"], row["first_price_denominator"])
close_raw = raw_ratio(row["last_price_numerator"], row["last_price_denominator"])
if open_raw is None or close_raw is None or open_raw == 0:
return None
return (close_raw - open_raw) / open_raw * 100
def format_fraction(value, digits=18):
"""Display only: round half to even at `digits` places with exact integer arithmetic."""
quotient, remainder = divmod(abs(value.numerator) * 10**digits, value.denominator)
twice = remainder * 2
if twice > value.denominator or (twice == value.denominator and quotient % 2 == 1):
quotient += 1
whole, fraction = divmod(quotient, 10**digits)
text = str(whole) if digits == 0 else f"{whole}.{fraction:0{digits}d}"
return f"-{text}" if value < 0 and quotient != 0 else textBackfilling 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:
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://api.blockvectra.com/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
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:
data.dex_prices: 15 CU / call- Backfilling 1 year of daily prices for 200 tokens: with a max span of 90 days per request, covering 365 days takes 5 chunks per token, totaling 1,000 calls. Total consumption is 15,000 CU (approx <0.1% of the free plan cycle allowance), about <$0.01 at list price.
- Daily maintenance (refreshing 200 tokens once per day): 200 calls/day (3,000 CU/day), totaling approximately 6,000 calls per 30-day cycle (90,000 CU, approx 0.3% of the free quota), about <$0.01/month at list price.
When scaling your backfill volume or requiring higher request concurrency, top up on-chain on the console Billing page to upgrade to a paid account. For active rates and unit conversions, see the Pricing page.
Next steps
- Browse the datasets directory to see every dataset BlockVectra indexes.
- See the free plan and pricing to check what your account includes.
- Log in to the console to create an API key.
Last updated: