v5-spot-indexer API

Read-only REST API over indexed SwapShell.SwapExecuted events, with execution-quality and average-cost realized PnL.

live base URL: this host /health /stats

Overview

  • Read-only. All endpoints are GET; no authentication.
  • Finalized data only. Only logs from finalized blocks are stored, so rows never change or roll back.
  • All swaps, including failures (success: false with a decoded reason).

Chains & contracts

ChainchainIdSwapShell
Arbitrum421610xF8698CA109583Ea2CBc57EF560d4Be2E0DF349A2
Robinhood Testnet466300x528B30910B3ef5a615cDC3847F273947dc474519
Robinhood Mainnet46630x4262efBd176F02824af27010bEa218429c33c7E8

Which chains are live depends on deployment config — call /stats to see what's actually indexed.

Conventions

These apply to every endpoint — read this first.

  • Big numbers are strings. Token amounts (uint256) and block numbers are returned as decimal strings, never JSON numbers. Parse with a bigint/decimal library.
  • Raw token amounts. amountIn/amountOut etc. are in the token's smallest unit — divide by 10^decimals (from the tokens map). e.g. "1500000" USDC (decimals 6) = 1.50 USDC.
  • USD fields are human decimals. Anything ending in Usd is already a normal decimal string like "12.34".
  • Addresses are lowercase (query params are lowercased server-side).
  • Timestamps are ISO 8601 UTC.
  • Token maps. Any response with token addresses also includes a tokens object keyed by "<chainId>:<address>" resolving symbol/decimals/name.
  • Query booleans are the literal strings true/false.

Pagination & errors

List endpoints use keyset (cursor) pagination, newest first: limit (default 50, max 500) and an opaque nextCursor you pass back as cursor. nextCursor: null means the end.

curl -s "/trades?chainId=42161&limit=100"
curl -s "/trades?chainId=42161&limit=100&cursor=<nextCursor>"

400 = invalid query/params; 404 = not found.

Endpoints

GET /stats

Per-chain indexing status: last indexed block, finalized head, lag (blocks behind), and trade count.

{
  "chains": [
    { "chainId": 42161, "name": "arbitrum",
      "lastIndexedBlock": "472437134", "finalizedHead": "472440000",
      "lag": "2866", "trades": 114 }
  ]
}
GET /stats/daily

Platform-wide daily spot metrics over a trailing window of whole UTC calendar days, oldest first. One row per day (days with no activity are included as zeros).

MetricDefinition
transactionsExecuted (successful) swaps that day
activeAddressesDistinct takers with ≥1 executed swap that day
newAddressesTakers whose first-ever executed swap landed that day
ParamTypeDescription
daysintTrailing UTC days to return, incl. today (default 30, max 365)
chainIdintRestrict to one chain (default: all indexed). With a chain set, newAddresses is first-ever on that chain.
curl -s "/stats/daily?days=7"
{
  "segment": "spot", "chainId": null, "timezone": "UTC", "days": 7,
  "rows": [
    { "date": "2026-07-09", "activeAddresses": 812, "newAddresses": 143, "transactions": 5127 },
    { "date": "2026-07-10", "activeAddresses": 934, "newAddresses": 121, "transactions": 6011 }
  ]
}

Only executed fills count — reverted swaps (and non-swap events the indexer never stores: cancels, transfers, deposits/withdrawals) are excluded. Today's row and short windows trail real time by the finality lag.

GET /trades

The trade feed. All filters optional, combined with AND.

ParamTypeDescription
chainIdintRestrict to one chain
takeraddressTrades by this taker
tokenIn / tokenOutaddressFilter by input/output token
routeraddressUnderlying router/venue
routeTagstringDecoded route tag
successbooltrue = successful only
pricedbooltrue = USD-priced (stablecoin leg) only
fromBlock / toBlockintInclusive block range
cursorstringPagination cursor
limitintPage size (default 50, max 500)

Returns { trades: Trade[], tokens: {…}, nextCursor }. See Data model.

curl -s "/trades?chainId=42161&success=true&limit=20"
GET /trades/{chainId}/{txHash}

All swaps emitted in a single transaction.

curl -s "/trades/42161/0xabc...def"
GET /takers/{chainId}/{address}/trades

Trades for one taker, newest first. Supports cursor and limit.

curl -s "/takers/42161/0x51cc...410c/trades?limit=50"
GET /takers/{chainId}/{address}/positions

Current inventory per token for a taker, with average cost and PnL.

{
  "positions": [
    { "token": "0xf97f4d...39fb4", "qty": "0.2551457815815209",
      "avgCostUsd": "7.838655954266621", "realizedPnlUsd": "0",
      "markPriceUsd": "7.84725886703017", "unrealizedPnlUsd": "0.0021949969",
      "lastBlockNumber": "472437134" }
  ],
  "tokens": { "42161:0xf97f4d...39fb4": { "symbol": "LINK", "decimals": 18, "…": "…" } }
}
FieldDescription
qtyRemaining held quantity (human units)
avgCostUsdAverage cost basis per unit, USD
realizedPnlUsdCumulative realized PnL for this token
markPriceUsdLatest observed execution price
unrealizedPnlUsd(markPrice - avgCost) * qty
lastBlockNumberBlock of the most recent trade in the position
GET /takers/{chainId}/{address}/portfolio

Historic total portfolio value (USD) for a taker, as a time series for charts (oldest first). A background job snapshots every tracked wallet on a fixed cadence (default 30 min), pricing each held token once via the chain's router and valuing the taker's positions. Cached (~5 min) with a Cache-Control header.

ParamTypeDescription
windowenum1d | 7d | 30d | 90d | all (default 30d)
curl -s "/takers/42161/0x51cc...410c/portfolio?window=30d"
{
  "chainId": 42161, "address": "0x51cc...410c", "window": "30d",
  "points": [
    { "capturedAt": "2026-06-16T00:00:00.000Z", "totalValueUsd": "1234.56",
      "pricedTokenCount": 3, "totalTokenCount": 4 }
  ]
}

Value is based on the derived positions (net tokens acquired via SwapShell), not on-chain balances. When pricedTokenCount < totalTokenCount the value is partial — some held tokens had no router liquidity to price.

GET /pnl/summary

Aggregate PnL/quality metrics, grouped. groupBy is required: taker, routeTag, tokenIn, or tokenOut. Accepts the same optional filters as /trades, plus limit.

curl -s "/pnl/summary?groupBy=taker&taker=0x51cc...410c"
{
  "groupBy": "taker",
  "groups": [
    { "key": "0x51cc...410c", "trades": 2, "successful": 2, "priced": 2,
      "usdNotional": "2.00", "priceImprovementUsd": "0.000294",
      "avgSlippageHeadroomUsd": "0.005120", "realizedPnlUsd": null }
  ],
  "tokens": {}
}

When grouping by a token, the tokens map resolves the address keys; for taker/routeTag it's empty.

GET /trading-stats

Spot trading stats for a single account. The address is a query param (not chain-path scoped like /takers/…).

ParamTypeDescription
addressaddressRequired. Account to report on
chainIdintRestrict to one chain (default: all enabled)
windowenum1d | 7d | 14d | 30d | all (default all)
curl -s "/trading-stats?address=0x51cc...410c&window=all"
{
  "address": "0x51cc...410c", "chainId": null, "window": "all",
  "spot": { "volumeUsd": "10280000.00", "feesUsd": "0", "trades": 128 }
}

volumeUsd = total USD notional of priced swaps in the window; feesUsd is currently always 0.

POST /trading-stats/batch

Spot trading stats for a list of accounts. Send a JSON body; get the combined total across all addresses plus a per-address breakdown. Addresses with no trades in the window are returned with zeros.

FieldTypeDescription
addressesaddress[]Required. 1–1000 accounts (de-duplicated)
chainIdintRestrict to one chain (default: all enabled)
windowenum1d | 7d | 14d | 30d | all (default all)
curl -s -X POST "/trading-stats/batch"   -H 'content-type: application/json'   -d '{"addresses":["0x51cc...410c","0xabcd...1234"],"window":"all"}'
{
  "chainId": null, "window": "all", "addressCount": 2,
  "total": { "volumeUsd": "10530000.00", "feesUsd": "0", "trades": 140 },
  "byAddress": [
    { "address": "0x51cc...410c", "volumeUsd": "10280000.00", "feesUsd": "0", "trades": 128 },
    { "address": "0xabcd...1234", "volumeUsd": "250000.00", "feesUsd": "0", "trades": 12 }
  ]
}

total.volumeUsd is summed in Postgres for full precision; per-address volumeUsd = total USD notional of priced swaps in the window.

GET /leaderboard

Platform-wide PnL leaderboard, aggregated per taker over a time window. When chainId is omitted, a taker's activity is summed across all enabled chains.

ParamTypeDescription
windowenum1d | 7d | 14d | 30d | all (default 30d)
chainIdintRestrict to one chain (default: all enabled)
orderByenumpnl (default) or volume
limitintPage size (default 50, max 500)
offsetintRows to skip — for numbered pages
curl -s "/leaderboard?window=30d&limit=10&offset=0"
{
  "window": "30d", "chainId": null, "orderBy": "pnl",
  "total": 247, "limit": 10, "offset": 0,
  "rows": [
    { "trader": "0x51cc...410c", "volumeUsd": "1203000.00",
      "feesUsd": "0", "realizedPnlUsd": "1230000.00", "trades": 42 }
  ]
}

PnL semantics: realized only — average-cost realized gains/losses from sells within the window. A buy-and-hold trader shows realizedPnlUsd: null for the window (their unrealized PnL is on /takers/…/positions). Short windows trail real time by the finality lag.

GET /tokens/volume

USD trading volume per token over a window (default the last 24 hours), ranked highest first. Each priced swap's usdNotional is attributed to its non-stable base token (the asset bought/sold against a stablecoin), so each trade counts once. Stablecoins, token-to-token swaps, and failed swaps have no base token / USD value and are excluded. Fetch a single token's 24h volume by passing its contract address (address/ca) or ticker symbol (symbol/ticker). Heavily cached (~5m).

ParamTypeDescription
windowenum1d | 7d | 14d | 30d | all (default 1d)
chainIdintRestrict to one chain (default: all enabled)
address / caaddressOnly this base-token contract address
symbol / tickerstringOnly base tokens with this symbol (case-insensitive)
limitintMax tokens (default 50, max 500)
curl -s "/tokens/volume?ticker=WETH&window=1d"
{
  "window": "1d", "chainId": null, "address": null, "symbol": "WETH",
  "rows": [
    { "chainId": 42161, "token": "0x82af49...fbab1",
      "volumeUsd": "1840293.55", "trades": 312 }
  ],
  "tokens": { "42161:0x82af49...fbab1": { "symbol": "WETH", "decimals": 18, "…": "…" } }
}

With no chainId, the same token on different chains appears as separate rows — and a shared symbol can match several tokens. Short windows trail real time by the finality lag.

GET /tokens/{chainId}/{address}

Cached metadata for one token (404 if unseen).

{ "chainId": 42161, "address": "0xaf88...5831",
  "symbol": "USDC", "name": "USD Coin", "decimals": 6,
  "underlying": null,              // wrapped token's underlying, else null
  "firstSeenBlock": "472212554" }
GET /token-metadata

Metadata for every tracked token in the served categories (meme and pToken by default), in one call. The token list is owned by the router (its /tokens route); this endpoint fetches it, keeps the served categories, and resolves each token's market metadata from the same source as /token-metadata/{address}. Each entry is an identical metadata record. priceUsd (with marketCap/fdv rescaled to match) is overlaid live from the streaming price source — see /token-metadata/{address}.

Partial results. A token with no market data (or a transient upstream hiccup) is omitted rather than failing the whole response — compare resolved vs requested. A 502 is returned only when the router token list itself can't be fetched.
curl -s "/token-metadata"
{
  "requested": 30, "resolved": 27,
  "tokens": [
    { "address": "0x020bfc...018b4", "symbol": "CASHCAT", "name": "Cash Cat",
      "priceUsd": "0.1231", "marketCap": 122122047, "liquidityUsd": 6147985.84,
      "imageUrl": "/token-images/a1b2c3...", "source": { "score": 1, "…": "…" } }
  ]
}

Each element of tokens has the same shape as /token-metadata/{address} (see there for field details and image handling).

GET /token-metadata/{address}

One authoritative metadata record for a token (price, liquidity, 24h volume, market cap, socials, logo), aggregated from public market data. priceUsd is overlaid live from the streaming price source (the same feed behind /token-price and /ws), with marketCap/fdv rescaled to match; the slower-moving fields (liquidity, socials, images) are cached per token (~60s) within a rate-limited budget. Display data only — not for settlement.

A token can trade in many pools, and anyone can create one with a spoofed name/image/socials. We never trade against these pools (the pairAddress is informational), but we still must pick which pool's metadata to trust. We rank all pools whose base token is the given address by economic activity — USD liquidity (dominant), 24h volume, total 24h transactions, and two-sidedness (min(buys,sells)/max(buys,sells)) — and return the winner. source.liquidityDominance (winner's share of total liquidity) and source.score (0–1 composite) express confidence.
ParamTypeDescription
addressaddressRequired. Base-token contract address
curl -s "/token-metadata/0x020bfc650a365f8bb26819deaabf3e21291018b4"
{
  "address": "0x020bfc...018b4",
  "chainId": "robinhood",
  "name": "Cash Cat", "symbol": "CASHCAT",
  "priceUsd": "0.1231", "priceNative": "0.00007077",
  "marketCap": 122122047, "fdv": 122122047,
  "liquidityUsd": 6147985.84, "volume24h": 100354315.08,
  "txns24h": { "buys": 32200, "sells": 25238 },
  "imageUrl": "/token-images/a1b2c3...", "header": "/token-images/d4e5f6...", "openGraph": "/token-images/7890ab...",
  "websites": [ { "url": "https://cashcattoken.xyz/", "label": "Website" } ],
  "socials":  [ { "type": "twitter", "url": "https://x.com/cashcat_token" } ],
  "source": {
    "dexId": "uniswap", "pairAddress": "0xa70f...e313", "labels": ["v3"],
    "score": 1, "liquidityDominance": 0.99998, "pairsConsidered": 5
  }
}

Numeric market fields (liquidityUsd, volume24h, marketCap, fdv) are JSON numbers; prices are strings. Covers every known chain/DEX (not limited to indexed chains). 404 when the token has no market data; 502 if the upstream is unreachable.

Image fields (imageUrl, header, openGraph) are same-origin /token-images/{id} paths served by the endpoint below — resolve them against this host. null when the token has no such asset.

GET /token-images/{id}

Streams a token image referenced by /token-metadata. The bytes are cached hard (~24h, immutable) and served from this origin. Not called directly — use the imageUrl/header/openGraph paths returned by /token-metadata.

404 if the id is unknown or expired (re-fetch the token's metadata for a fresh path); 502 if the image can't be retrieved.

GET /token-price/{address}

Current USD price for a token, served from the live streaming price cache. A token someone is watching answers instantly from memory; a cold token costs one upstream query and joins the live subscription set so subsequent reads are instant. For a push feed instead of polling, use /ws.

ParamTypeDescription
networkIdintPrice-provider network id (defaults to the configured chain)
curl -s "/token-price/0x020bfc650a365f8bb26819deaabf3e21291018b4"
{
  "address": "0x020bfc...018b4", "networkId": 4663,
  "priceUsd": 0.15149986707, "timestamp": 1783662870, "source": "ws"
}

timestamp is the upstream price observation (unix seconds). source is ws when the value came off the live stream, query for a cold/fallback read. 503 when live prices aren't configured; 404 when the token can't be priced; 502 if the upstream is unreachable.

GET /token-chart/{address}

Point-chart data for a token — one close price (USD) and volume per time bucket, oldest first. Meant for a simple line chart; for candlesticks use /token-candles.

Smart caching: closed buckets never change, so once fetched they're kept and reused forever; a request only fetches the sub-ranges it has never seen plus the still-forming current bucket. Widening or shifting the time window reuses everything already cached and fetches just the new slice. While a token's candles are being live-streamed over /ws, the current bucket is served from the stream with no upstream fetch at all.
ParamTypeDescription
resolutionenum1m | 5m | 15m | 1h | 1d (default 1h)
networkIdintChart-provider network id (defaults to the configured chain)
from / tointUnix seconds. Default: the most recent 300 buckets. The span is clamped to 5000 points.
curl -s "/token-chart/0x020bfc650a365f8bb26819deaabf3e21291018b4?resolution=1h&networkId=4663"
{
  "address": "0x020bfc...018b4", "networkId": 4663, "resolution": "1h",
  "from": 1782856800, "to": 1783537200,
  "points": [
    { "t": 1782856800, "price": 0.0912, "volume": 48210.44 },
    { "t": 1782860400, "price": 0.0987, "volume": 51877.10 }
  ]
}

t is the bucket start (unix seconds); price is the bucket close; volume is quote volume. 503 when charting isn't configured; 400 for a bad range; 502 if the upstream is unreachable.

GET /token-candles/{address}

Full OHLCV candles for a token, oldest first — same windowing, parameters, and smart caching as /token-chart, but each bucket keeps its open/high/low/close. Pair this (history) with the /ws candles channel (live updates) to drive a candlestick chart.

ParamTypeDescription
resolutionenum1m | 5m | 15m | 1h | 1d (default 1h)
networkIdintChart-provider network id (defaults to the configured chain)
from / tointUnix seconds. Default: the most recent 300 buckets. The span is clamped to 5000 points.
curl -s "/token-candles/0x020bfc650a365f8bb26819deaabf3e21291018b4?resolution=1m"
{
  "address": "0x020bfc...018b4", "networkId": 4663, "resolution": "1m",
  "from": 1783644881, "to": 1783662881,
  "candles": [
    { "t": 1783644900, "o": 0.001408, "h": 0.001409, "l": 0.001394, "c": 0.001394, "volume": 497.97 },
    { "t": 1783644960, "o": 0.001394, "h": 0.001401, "l": 0.001394, "c": 0.001401, "volume": 285.05 }
  ]
}

Buckets with no trades are omitted. Same error semantics as /token-chart.

Live streaming — WS /ws

WebSocket endpoint for live prices and candles. Connect to wss://<host>/ws and exchange JSON messages; one connection multiplexes any number of subscriptions (up to the per-connection cap, default 50). Upstream market-data subscriptions are shared across all clients watching the same token, so subscribing is cheap — never poll the REST endpoints when you can hold a socket.

Client → server

{ "op": "subscribe",   "channel": "price",   "address": "0x…" }
{ "op": "subscribe",   "channel": "candles", "address": "0x…", "resolution": "1m" }
{ "op": "unsubscribe", "channel": "price",   "address": "0x…" }
{ "op": "unsubscribe", "channel": "candles", "address": "0x…", "resolution": "1m" }
{ "op": "ping" }

resolution1m | 5m | 15m | 1h | 1d (required for candles). networkId is optional and defaults to the configured chain. Addresses must be tokens from the router catalog — unknown addresses are rejected with unknown_token.

Server → client

// acks and errors
{ "op": "subscribed", "channel": "price", "address": "0x…", "networkId": 4663 }
{ "op": "unsubscribed", "channel": "candles", "address": "0x…", "networkId": 4663, "resolution": "1m" }
{ "op": "pong" }
{ "op": "error", "code": "unknown_token", "message": "address is not a tracked token" }

// data — price: pushed on every price change (plus a snapshot on subscribe)
{ "channel": "price", "address": "0x…", "networkId": 4663,
  "data": { "priceUsd": 0.001651, "timestamp": 1783663007 } }

// data — candles: the currently-forming bar, resent on every update;
// a new "t" means the previous bar closed
{ "channel": "candles", "address": "0x…", "networkId": 4663, "resolution": "1m",
  "data": { "t": 1783662900, "o": 0.001651, "h": 0.001653, "l": 0.001650, "c": 0.001652, "volume": 120.5 } }

Error codes: bad_message, bad_network, unknown_token, over_limit, over_capacity, validation_unavailable, not_configured, internal. On subscribe you get an immediate snapshot when one is available (always for price; for candles once the stream has produced a bar — render REST history first). The server pings every 30s; unresponsive connections are dropped, so reconnect with backoff and re-subscribe.

CoinGecko

Public, no-auth endpoints in CoinGecko's Spot Exchanges API shape, serving a single spot chain (the Robinhood mainnet market). Prices are quoted in the market's stablecoin (the "target"), computed directly from each swap's two legs, so they're independent of any USD peg. As an RFQ/aggregator (no pool, no native order book) we omit pool_id, liquidity_in_usd, and the /orderbook endpoint, and don't report bid/ask — confirmed acceptable by CoinGecko. Only successful, finalized, single-stablecoin-leg swaps appear; wrapped legs are resolved to their underlying, so markets are keyed by the real asset.

GET /tickers

24-hour market stats for every market that has ever traded, most-active first. last_price is the most recent trade ever (so a market idle in the last 24h still reports a price); base_volume/target_volume/high/low are over the trailing 24h (high/low are null with no 24h trades). Cached ~30s.

[
  { "ticker_id": "0x1f9840...1f984_0x5fc536...1d168",
    "base_currency": "0x1f9840...1f984",
    "target_currency": "0x5fc536...1d168",
    "last_price": "231.55", "base_volume": "1240.5", "target_volume": "287000.0",
    "high": "233.10", "low": "229.80" }
]

ticker_id = <baseAddress>_<targetAddress> (lowercased). All amounts are decimal strings.

GET /historical_trades

Completed trades for one market, newest first, split into buy/sell arrays.

ParamTypeDescription
ticker_idstringRequired. <baseAddress>_<targetAddress>
typeenumbuy | sell — restrict to one side
limitintMax trades (default 200, max 1000)
start_time / end_timeintUnix seconds; inclusive time range
curl -s "/historical_trades?ticker_id=0x1f9840...1f984_0x5fc536...1d168&limit=2"
{
  "buy": [
    { "trade_id": 84213, "price": "231.55", "base_volume": "5.0",
      "target_volume": "1157.75", "trade_timestamp": 1782438650123, "type": "buy" }
  ],
  "sell": []
}

trade_timestamp is Unix milliseconds; trade_id is the indexer's stable row id. type: buy = base bought with the stablecoin, sell = base sold for it.

CoinMarketCap

Public, no-auth endpoints in CoinMarketCap's Section A — Spot Exchanges shape, serving the same single spot chain as the CoinGecko endpoints. Endpoints are versioned per CMC's integration standards — /cmc/v1/… is canonical (unversioned /cmc/… aliases also work). Pairs are symbol-based (e.g. GLW_USDG): tokens are ranked by 24h activity and the busiest token wins its symbol; a token whose symbol is already taken (or that has no on-chain symbol) is excluded rather than served ambiguously. As an RFQ/aggregator there is no resting order book, so the /orderbook endpoint is not provided; summary's lowest_ask/highest_bid are instead synthesized from live router quotes (see below).

GET /cmc/v1/summary

Overview of every market: 24h stats plus price_change_percent_24h (last price vs. the last trade at-or-before 24h ago, falling back to the first trade inside the window; null when no reference exists). last_price is the most recent trade ever; highest_price_24h/lowest_price_24h are null with no 24h trades. Cached ~30s.

lowest_ask/highest_bid are the live executable RFQ prices for a ~$100-notional swap (configurable via CMC_BBO_NOTIONAL_USD) in each direction, quoted through the chain's router at snapshot build time — the honest equivalent of a top-of-book on a venue with no resting orders. Either side is null when no venue quotes it (or BBO is disabled via CMC_BBO_ENABLED=false).

[
  { "trading_pairs": "GLW_USDG",
    "base_currency": "GLW", "quote_currency": "USDG",
    "last_price": "231.55", "lowest_ask": "231.98", "highest_bid": "231.12",
    "base_volume": "1240.5", "quote_volume": "287000.0",
    "price_change_percent_24h": -1.32,
    "highest_price_24h": "233.10", "lowest_price_24h": "229.80" }
]

Amounts are decimal strings; price_change_percent_24h is a JSON number.

GET /cmc/v1/assets

Every currency participating in a served market, keyed by symbol. Assets settle on-chain via swaps (no custody gate, no exchange-level fees), so can_deposit/can_withdraw are always "true" and maker_fee/taker_fee are "0". contractAddressUrl appears only when an explorer URL is configured (CMC_EXPLORER_TOKEN_URL). Cached ~30s.

{
  "GLW": { "name": "Glow", "contractAddress": "0x1f9840...1f984",
    "can_deposit": "true", "can_withdraw": "true",
    "maker_fee": "0", "taker_fee": "0" }
}
GET /cmc/v1/ticker

24h pricing/volume per market, keyed by pair. Same data as /cmc/v1/summary in CMC's ticker shape. Cached ~30s.

{
  "GLW_USDG": { "last_price": "231.55",
    "base_volume": "1240.5", "quote_volume": "287000.0", "isFrozen": "0" }
}
GET /cmc/v1/trades/{market_pair}

Recently completed trades for one market, newest first, as a flat array (default 1000 — comfortably more than 24h of fills).

ParamTypeDescription
market_pairstringRequired (path). Symbol pair, e.g. GLW_USDG (case-insensitive)
typeenumbuy | sell — restrict to one side
limitintMax trades (default 1000, max 1000)
curl -s "/cmc/v1/trades/GLW_USDG?limit=2"
[
  { "trade_id": 84213, "price": "231.55", "base_volume": "5.0",
    "quote_volume": "1157.75", "timestamp": 1782438650123, "type": "buy" }
]

timestamp is Unix milliseconds; trade_id is the indexer's stable row id. type: buy = base bought with the stablecoin, sell = base sold for it. 400 for an unknown pair.

Data model

Trade object

{
  "id": "1234",                    // stable unique row id (string)
  "chainId": 42161,
  "blockNumber": "472437134",
  "blockTimestamp": "2026-06-16T02:38:50.775Z",
  "txHash": "0x...", "logIndex": 3,
  "swapShell": "0x...",            // emitting contract
  "taker": "0x...",
  "tokenIn": "0x...", "tokenOut": "0x...",       // underlying (wrapped legs resolved)
  "tokenInWrapped": false, "tokenOutWrapped": true,  // which legs settled wrapped
  "rawTokenIn": "0x...", "rawTokenOut": "0x...",  // raw on-chain legs (wrapped addr when *Wrapped)
  "minAmountOut": "...",           // signed slippage floor (raw)
  "amountIn": "...", "amountOut": "...",            // raw base units (amountIn as emitted)
  "effectiveAmountIn": "...",      // raw input the taker actually spent; = amountIn unless the
                                   // router refunded unused input in the same tx. All USD
                                   // economics (usdNotional, tokenExecPriceUsd, baseQty, PnL)
                                   // derive from this, not amountIn.
  "quotedAmountIn": "...", "quotedAmountOut": "...",
  "tokenInBenchmarkPrice": "...", "tokenOutBenchmarkPrice": "...",  // often 0/unreliable
  "router": "0x...",
  "routeTag": "zerox",             // decoded, or null
  "routeTagRaw": "0x...",
  "success": true, "reason": "",
  "pnl": { /* see below */ }
}

The pnl object (per trade)

USD value is anchored to the swap's stablecoin leg (configured per chain), since the event's benchmark prices are often zero.

"pnl": {
  "priced": true,                  // false if no stablecoin leg / failed / unknown decimals
  "usdNotional": "151.90",         // trade size in USD
  "feeUsd": "0",                   // per-trade USD fee (0 until a fee source exists)
  "tokenExecPriceUsd": "7.84",     // implied USD price of the non-stable token
  "priceImprovementUsd": "0.0003", // amountOut - quotedAmountOut, in USD
  "slippageHeadroomUsd": "0.005",  // output above signed minAmountOut, in USD
  "side": "buy",                   // "buy" | "sell" | null
  "baseToken": "0x...", "baseQty": "0.255",
  "realizedPnlUsd": "1.20",        // average-cost realized PnL (null until accounted)
  "avgCostUsd": "7.50",
  "matchedQty": "0.1",             // qty matched against inventory on a sell
  "positionQtyAfter": "0.155"
}

Side: buy = stablecoin sold for the token; sell = token sold for a stablecoin; null = stable-stable or token-token swap.

Wrapped tokens: a wrapped fill settles the taker in a non-transferable wrapped representation (e.g. wTSLA) of the token actually bought (TSLA); the real underlying is delivered later off the SwapExecuted path. The indexer normalizes it — tokenIn/tokenOut, baseToken, side, pricing, positions, and volume all use the underlying (so a wrapped buy nets against a later real sell). tokenInWrapped/tokenOutWrapped flag which legs settled wrapped, and rawTokenIn/rawTokenOut keep the raw on-chain address.

PnL explained

There are two distinct concepts:

Execution PnL (per trade, stateless)

priced, usdNotional, tokenExecPriceUsd, priceImprovementUsd, slippageHeadroomUsd describe the quality of a single execution. Always available for priced trades, including backfilled ones.

Realized PnL (average cost, stateful)

realizedPnlUsd, avgCostUsd, matchedQty, positionQtyAfter (and the /positions endpoint) track profit/loss over a sequence of trades per (taker, token): a buy updates the average cost; a sell realizes (execPrice - avgCost) * matchedQty. Computed by a stateful pass over each key's full history, so it stays correct even when older trades are backfilled later.

Caveats

  • Partial positions. Only SwapShell swaps are observed. Tokens acquired/disposed elsewhere aren't counted; sells beyond observed inventory match only the held qty and the position floors at zero (no shorts).
  • priced: false when a swap failed, has no configured stablecoin leg, or the token's decimals aren't known — USD fields are null.
  • Benchmark prices from the event are frequently zero/unreliable; prefer the stablecoin-anchored pnl.*Usd fields.
  • Lag. Only finalized blocks are indexed; newest trades appear after finality. Check /stats.