TokenPrices: onchain token prices, read directly from DEX liquidity.
Prices for a specific chain and contract, with the pool, block and route shown, and clear limits on what we cover.
TokenPrices computes USD prices from raw DEX pool state read over JSON-RPC at a pinned, finality-tagged block (a finalized slot on Solana, a closed ledger on Stellar). It does not resell or proxy any other price feed. Each response names the pool(s) and route (at most 2 hops to a USD anchor), the exact block, a quality status, the basis and the USD anchor assumption. When we cannot qualify a price we return price_usd: null and a reason. We never make up a price.
Machine-readable contract: /openapi.json (OpenAPI 3.1).
Quick start
Examples use BASE=http://127.0.0.1:8788. Every endpoint is a plain GET.
BASE=http://127.0.0.1:8788
# Current spot price of WETH on Base
curl -s "$BASE/v1/prices/base/0x4200000000000000000000000000000000000006"
# Effective sell price for $1,000 of WETH (fees + price impact included)
curl -s "$BASE/v1/prices/base/0x4200000000000000000000000000000000000006?mode=indicative¬ional_usd=1000"
# Also serve thin-market (indicative) prices instead of null
curl -s "$BASE/v1/prices/base/0x4200000000000000000000000000000000000006?min_quality=indicative"
# Solana: WSOL by its mint (base58, case-sensitive)
curl -s "$BASE/v1/prices/solana/So11111111111111111111111111111111111111112"
# Several tokens in one request (up to 50, answered in request order; costs 5 units)
curl -s "$BASE/v1/prices/base?contracts=0x4200000000000000000000000000000000000006,0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
# Every priced token on a chain (paginated, up to 200 per page; costs 5 units)
curl -s "$BASE/v1/prices/solana?limit=200"
# Last 24 h of 5-minute samples, hourly buckets
curl -s "$BASE/v1/prices/base/0x4200000000000000000000000000000000000006/history?interval=1h"
# Find a token, list pools, check health
curl -s "$BASE/v1/tokens/search?q=WETH"
curl -s "$BASE/v1/pools?chain=base&eligible_only=true"
curl -s "$BASE/v1/health"
# With an API key (higher rate limit)
curl -s -H "x-api-key: tp_..." "$BASE/v1/chains"
Conventions
- Always check
price_usd !== null, not only the HTTP status. Price endpoints answer 200 for any syntactically valid chain and address, including unknown tokens;qualityandreasonexplain a null. - Chains:
ethereum,base,arbitrum,robinhood,bnb,avalanche(EVM),solanaandstellar. Any other value gives 404UNSUPPORTED_CHAIN. - Addresses: on EVM chains
0x+ 40 hex characters, case-insensitive; token addresses come back EIP-55 checksummed, pool addresses lowercase. On Solana, base58 public keys, case-sensitive, returned exactly as stored. On Stellar, see Stellar. An address of the wrong family for the chain gives 400INVALID_ADDRESS. - Prices, USD amounts and block numbers are decimal strings (up to 18 significant digits, never exponent notation). Small integers (bps, ppm, counts, ages) are JSON numbers.
- Times are ISO 8601 UTC with second precision, e.g.
2026-11-11T12:00:00Z. Time query params also accept unix seconds. - Quality gate: by default only
QUALIFIEDprices are served.min_quality=indicativealso servesINDICATIVE_*(warningINDICATIVE_SERVED);allow_stale=trueserves the last sample whenSTALE(warningSTALE_SERVED). A withheld price getsreason: BELOW_MIN_QUALITY(underlying ones inreasons). - Every response carries
X-Request-Id; error bodies repeat it asrequest_id. CORS is open (*) for GET, HEAD and OPTIONS. - Pagination:
limit(default 50, max 200) andoffset(max 10,000); responses includelimit,offset,total.
Endpoints
GET/v1/prices/{chain}/{contract}
Current spot or indicative USD price, with route, block, depth and quality.
| Param | Values | Default | Notes |
|---|---|---|---|
mode | spot | indicative | spot | Indicative = effective price when selling notional_usd of the token, including fees and price impact. Not a firm quote. |
notional_usd | 100 | 1000 | 10000 | 100 | Used by mode=indicative; with spot it picks the cost shown in depth. |
min_quality | qualified | indicative | qualified | See the quality gate above. |
allow_stale | true | false | false | Serve the last sampled price when stale. |
Response fields: price_usd, basis (onchain_spot|onchain_indicative|usd_peg_assumption), quality, reason, reasons, estimated_usd_anchor (e.g. USDC≈USD), as_of (block, hash, times, finality), source (pool, protocol, route, hops; hop fee_ppm = total fee, v4 protocol fee included), depth, shares_per_token (issuer multiplier; pending change in next), price_usd_per_ui_unit (see Solana), warnings.
GET/v1/prices/{chain}
Many prices in one request; costs 5 rate-limit units. With contracts (1–50 comma-separated addresses) you get one item per address in request order, unknown addresses included as UNSUPPORTED_TOKEN; limit/offset are null. Without contracts you get every sampled token on the chain, ordered by address, paginated by limit (default 100, max 200) and offset, with total. Query params apply to every item. An invalid item: 400 INVALID_ADDRESS (details.index).
GET/v1/prices/{chain}/{contract}/history
Stored end-of-block samples. Points below the quality gate are omitted. Costs 5 rate-limit units.
| Param | Values | Default | Notes |
|---|---|---|---|
from, to | ISO 8601 or unix seconds | last 24 h | Range at most 7 days (2016 points at 5m). |
interval | 5m | 1h | 1d | 5m | 1h/1d: OHLC bars, price_usd = close; max 90 / 2,190 days. |
min_quality | qualified | indicative | qualified |
GET/v1/prices/{chain}/{contract}/chart
Fixed-range series: range = 24h (5m), 7d|30d|90d (1h), 1y|max (1d); default 7d. Same response as /history plus range. 5 units.
GET/v1/prices/{chain}/{contract}/ticks
Recent per-swap (Solana: per-state) prices from the real-time engine, in memory only. since (default now − 10 min, at most 60 min back), limit 1–500. Empty with reason when gated or the engine is unavailable. 5 units.
GET/v1/prices/{chain}/{contract}/depth
Depth chart of DEX liquidity (AMM, not resting orders): cumulative bids/asks per level over ±range% (0.1–20, default 10) in buckets (1–200, default 50), per-pool split, impact ladder (0.5/1/2/5%). 5 units.
GET/v1/pools
Registered pools with their latest state (prices both ways, liquidity, cost, tier, reasons). Active pools only by default (total = counts.pools). Pool fee_ppm is the LP fee tier (it excludes any v4 protocol fee).
| Param | Values | Default | Notes |
|---|---|---|---|
chain | chain key | required | |
token | address | Pools containing this token. | |
protocol | uniswap-v2, uniswap-v3, aerodrome-volatile, aerodrome-stable, aerodrome-slipstream, uniswap-v4, pancakeswap-v3, pancakeswap-v2; Solana: raydium-amm-v4, raydium-cpmm, raydium-clmm, orca-whirlpool, meteora-dlmm; Stellar: stellar-classic-amm, aquarius-cp|stable, sushi-v3-stellar | Meteora DLMM pools carry bin_step. | |
eligible_only | true | false | false | Only tier A or B pools. |
include_inactive | true | false | false | Also list seeded pools that are not sampled (active: false). |
limit, offset | integers | 50, 0 |
GET/v1/tokens/search
Search curated tokens by full address, symbol, name prefix or asset ticker. Results include verified, quality and the default-gate price_usd.
| Param | Values | Default | Notes |
|---|---|---|---|
q | 1 to 64 characters | required | |
chain, class | chain key, asset class | ||
limit | 1 to 50 | 20 |
GET/v1/tokens/{chain}/{contract}
Token metadata, a price summary and up to 20 pools. Unlike the price endpoint, an unknown token is 404 NOT_FOUND (catalogue lookup).
GET/v1/assets
Curated assets with asset_class, tokenized, form and stablecoin peg (asset_type deprecated). Only USD stablecoins are $1 anchors; catalogue_only tokens (e.g. BUIDL, BENJI) are never priced.
| Param | Values | Notes |
|---|---|---|
class, tokenized, form | enum, true|false, enum | Taxonomy filters (type: deprecated). |
issuer | string | Exact, case-insensitive. |
q | 1 to 64 characters | Ticker or name prefix. |
limit, offset | integers |
GET/v1/assets/{id}
One asset (e.g. crypto:ETH, URL-encoded) with its tokens. Unknown id: 404 NOT_FOUND.
GET/v1/icons/chain/{chain}, /v1/icons/protocol/{protocol}, /v1/icons/asset/{id}, /v1/icons/token/{chain}/{contract}
One icon as {kind, id, icon}; redirect=1&size=64: 302 to the image. Catalogue items carry icon (pools: protocol_icon).
GET/v1/chains
Chains: status, finality, protocols, anchors, counts, last sample. finality_tag is what is pinned (Robinhood: latest, confirmations: 30). Each chain has a family (evm, svm or stellar); chain_id is null off EVM.
GET/v1/health
Service and sampler health: ok when every active chain has a good run within 15 minutes, degraded otherwise, and 503 down if the database is unreachable.
GET/openapi.json
The OpenAPI 3.1 document for this API.
Quality statuses
Every price carries one quality. Only QUALIFIED is served by default.
| Status | Served by default | Meaning |
|---|---|---|
| QUALIFIED | yes | The best route is tier A at every hop, with no divergence, in bounds and fresh. |
| INDICATIVE_LOW_LIQUIDITY | with min_quality=indicative | The best route is tier B: thin liquidity, or high cost at $100. |
| INDICATIVE_DIVERGENT | with min_quality=indicative | Another tier-A route disagrees by more than 500 bps. |
| INDICATIVE_UNITS_UNVERIFIED | with min_quality=indicative | The token's units are ambiguous (unknown rebase type, or a non-1:1 wrapper whose conversion we do not read); the price is per pool accounting unit. Tokenized stocks with a share multiplier are not in this bucket: their price is USD per whole token and the multiplier is shown as shares_per_token. |
| STALE | with allow_stale=true | The last good sample is older than 15 minutes, its block is too old, or it was invalidated by a reorg. |
| NO_RELIABLE_MARKET | never | Pools exist but none pass the eligibility rules, or the price is out of bounds. |
| ANCHOR_UNAVAILABLE | never | Every candidate route needs an intermediate anchor (e.g. WETH) that could not be priced at tier A. |
| NOT_YET_SAMPLED | never | The token is in the registry but has not been sampled yet. |
| UNSUPPORTED_TOKEN | never | The chain and contract are not in the curated registry, or the token is inactive. |
Pool tiers: A = liquidity at least $10,000 and cost at most 200 bps for $100; B = indicative only; X = ineligible (liquidity under $1,000, cost over 1,000 bps, or read failure). Liquidity is 2 × the smaller side in USD.
Reason codes
reason is the most specific code; reasons lists all of them, gate reason first.
| Code | Meaning |
|---|---|
BELOW_MIN_QUALITY | A price exists but is below the requested min_quality. |
NO_POOLS | No registered pools for the token. |
NO_ROUTE_TO_ANCHOR | No 1 or 2 hop route reaches a USD anchor. |
POOL_READ_FAILED | Reading pool state failed at the pinned block. |
ZERO_LIQUIDITY | Pool has zero liquidity or a zero price. |
LIQUIDITY_BELOW_MINIMUM | Liquidity under $1,000 (tier X). |
LIQUIDITY_BELOW_QUALIFIED | Liquidity under $10,000 (tier B). |
PRICE_IMPACT_TOO_HIGH | Selling $100 costs more than 1,000 bps (tier X). |
PRICE_IMPACT_ABOVE_QUALIFIED | Selling $100 costs more than 200 bps (tier B). |
INSUFFICIENT_DEPTH | Not enough depth for the requested notional. |
QUOTE_UNAVAILABLE | The quote for that notional failed. |
ANCHOR_UNAVAILABLE | The intermediate anchor could not be priced. |
PRICE_OUT_OF_BOUNDS | Computed price outside 1e-18 to 1e12 USD. |
DIVERGENT_MARKETS | Qualified routes disagree by more than 500 bps. |
STALE_SAMPLE | Last sample older than 15 minutes. |
BLOCK_TOO_OLD | The sampled block is more than 30 minutes old. |
REORGED | The sampled block was reorged out. |
NOT_YET_SAMPLED | No sample yet. |
TOKEN_NOT_IN_REGISTRY | Unknown chain and contract pair. |
TOKEN_INACTIVE | Token is in the registry but inactive. |
CHAIN_INACTIVE | The chain is not being sampled. |
PROBE_TOO_LARGE, DECODE_ERROR, UNKNOWN_FACTORY | Pool-level failures: quote probe too large, undecodable return data, or a pool whose factory is not in the registry. |
UNKNOWN_PROGRAM, POOL_ACCOUNT_MISMATCH | Solana pool-level failures: the pool account is not owned by a registry program, or its mints/vaults differ from the registry. |
ANCHOR_DEPEGGED | A USD stablecoin anchor is over 50 bps from $1 against the chain's other stablecoins (recovers under 30 bps): priced from markets, routes through it repriced and capped at indicative. |
DEPTH_UNAVAILABLE | No depth book (engine down, none stored); 404 detail. |
MINT_READ_FAILED, TOKEN_PAUSED, TRANSFER_HOOK_UNSUPPORTED, TOKEN_EXTENSION_UNSUPPORTED | Solana Token-2022 gating: the mint could not be read, is paused, has an unapproved transfer hook, or is non-transferable. The token is not priced. |
ISSUER_AUTH_REQUIRED, ISSUER_READ_FAILED | Stellar: the issuer must authorize every holder (not an open market), or its account could not be read. Not priced. |
Warnings
| Code | Meaning |
|---|---|
USD_PEG_ASSUMED | A USD stablecoin anchor is valued at exactly 1 USD. |
ANCHOR_DEPEG_SUSPECTED | Stablecoin anchors disagree with each other. |
ANCHOR_PEG_UNVERIFIED | The USD stablecoin this price rests on could not be checked against other markets; still valued at $1 (see source.anchor_peg). |
QUOTE_UNAVAILABLE | A depth quote failed; the pool was capped at tier B. |
PROVISIONAL_FINALITY | Read at latest (not yet safe or finalized), e.g. Robinhood Chain. |
FINALITY_TAG_FALLBACK | The RPC rejected the finality tag; read at latest minus N blocks. |
REBASING_OR_WRAPPED_TOKEN | The token rebases or has an issuer share multiplier (shares_per_token); the price is USD per 1 whole token as traded. Unknown units: INDICATIVE_UNITS_UNVERIFIED. |
STALE_SERVED, INDICATIVE_SERVED | A stale or indicative price was served because you asked for it. |
DEPTH_ESTIMATED | A concentrated-liquidity pool on the route (Solana, Sushi on Stellar) was quoted with a single-range estimate. |
TRANSFER_FEE | The Token-2022 mint charges (or has scheduled) a transfer fee; it is included in depth quotes. |
TRANSFER_HOOK_PRESENT, PERMANENT_DELEGATE, PAUSABLE | Informational Token-2022 facts: an approved transfer hook, an issuer permanent delegate, or a pausable (not paused) mint. |
MULTIPLIER_CHANGE_PENDING | A scheduled multiplier change; see shares_per_token.next. |
AUTH_REVOCABLE, CLAWBACK_ENABLED | Stellar: the issuer can freeze holders, or claw balances back. |
Solana
- Slots and finality:
as_of.block_numberis the slot andas_of.block_hashthe base58 blockhash. Reads are atfinalizedcommitment (no reorgs); a pool's accounts share one slot, pools may differ by a few slots. - DEXes: Raydium AMM v4/CPMM/CLMM, Orca Whirlpools, Meteora DLMM; quotes computed locally.
- Token-2022: paused mints, unapproved transfer hooks and non-transferable mints are never priced (
TOKEN_PAUSED,TRANSFER_HOOK_UNSUPPORTED,TOKEN_EXTENSION_UNSUPPORTED). Transfer fees (charged in depth quotes), approved hooks, permanent delegates and pausable mints add warnings./v1/tokens/solana/{mint}showstoken_programandtoken_extensions. - UI amounts (xStocks, bStocks):
price_usdis always USD per 1 whole raw token. Wallets show UI amounts = raw × multiplier. Value a wallet balance asui_amount × price_usd_per_ui_unit(=price_usd / shares_per_token.value, on every chain when the multiplier is read onchain), neverui_amount × price_usd. A scheduled multiplier change appears inshares_per_token.nextwith warningMULTIPLIER_CHANGE_PENDING.
Stellar
- Tokens are keyed by Soroban contract id (
C…; classic assets: their asset contract). Pass that,CODE:ISSUERornative. block_numberis the ledger (final when closed). History starts at our first sample: no backfill.- Permissioned funds (BENJI, Spiko): listed, not priced.
Errors
{ "error": { "code": "INVALID_ADDRESS", "message": "contract must be a 20-byte hex address", "details": { "param": "contract" } },
"request_id": "6f1c2b8e-3a1d-4b44-9a57-1f3c0e0b9d21" }
| HTTP | Code | When |
|---|---|---|
| 400 | INVALID_PARAMETER | Bad enum, range or time, or a range that is too large. |
| 400 | INVALID_ADDRESS | Malformed address. |
| 400 | UNSUPPORTED_NOTIONAL | notional_usd not 100, 1000 or 10000. |
| 401 | INVALID_API_KEY | Unknown or revoked x-api-key. |
| 404 | UNSUPPORTED_CHAIN | Chain not supported. |
| 404 | NOT_FOUND | Unknown route, asset or catalogue token. |
| 405 | METHOD_NOT_ALLOWED | Anything but GET, HEAD or OPTIONS on /v1. |
| 429 | RATE_LIMITED | Rate limit exceeded; see Retry-After. |
| 500 | INTERNAL | Unexpected error. |
| 503 | DB_UNAVAILABLE | Database unavailable. |
Rate limits and API keys
- Anonymous: 60 units per minute per IP. With an
x-api-keyheader: the key's limit (default 600 per minute). - Each request costs 1 unit;
/historyand the batch/list/v1/prices/{chain}cost 5. - Every
/v1response (including errors) carriesRateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds); only 405, CORS preflights and 401INVALID_API_KEYomit them. Over the limit: 429RATE_LIMITEDwithRetry-After. - JSON responses are
application/json; charset=utf-8. - Limits are per edge location, best-effort.
- Prices are cached 30 s, history 5 minutes; samples come every 5 minutes, so faster polling gains nothing.
Coverage and limits
We would rather return null with a reason than a number we cannot stand behind. Read these limits before relying on a price.
- Chains: Ethereum, Base, Arbitrum, Robinhood Chain, BNB Chain (bStocks), Avalanche, Solana (xStocks) and Stellar.
- DEXes: Uniswap v2/v3/v4 (v4 without hooks), Aerodrome volatile/stable/Slipstream; PancakeSwap v3/v2 on BNB Chain; on Solana Raydium (AMM v4, CPMM, CLMM), Orca Whirlpools and Meteora DLMM; on Stellar the classic AMM, Aquarius and SushiSwap V3. Hooked v4 pools, Curve, Balancer, LFJ and PancakeSwap off BNB Chain are not read (tokens there may look thinner).
- Curated catalogue: only tokens and pools in our registry (liquid crypto plus tokenized stocks with real pools). Unknown addresses return
UNSUPPORTED_TOKEN; requests never trigger discovery. Prices are keyed by chain and contract, never by symbol. - Samples, not trades: each chain is sampled every 5 minutes at a pinned block (finality-tagged; Robinhood Chain uses
latestand is flaggedPROVISIONAL_FINALITY). Prices are end-of-block pool state, not executed trade prices or VWAPs. History gaps mean no qualifying sample. - USD means a stablecoin: USD is reached through stablecoin anchors (USDC, USDT, ...) assumed to equal 1 USD (
USD_PEG_ASSUMED). Routes are at most 2 hops. - Tokenized stocks are priced only from their own onchain pools. We never infer issuer NAV, the underlying share price, oracle prices or RFQ quotes, and many stock tokens are too thin to be
QUALIFIED. - Indicative prices are estimates of selling a fixed notional at the sampled block, not firm quotes. We do not execute trades.
TokenPrices API v1 · openapi.json · health · Attributions & removal
Logos and icons are trademarks of their respective owners. TokenPrices shows them unaltered, only to identify the chain, protocol or token concerned. Their display does not imply any affiliation with or endorsement by the owner. To request removal, contact legal@tokenprices.xyz.