{"openapi":"3.0.3","info":{"title":"Merkl Analytics API","description":"API for tracking DeFi positions, analyzing transactions, and computing PnL across protocols","version":"1.0.0"},"tags":[{"name":"Positions","description":"Retrieve and explore user positions across DeFi opportunities (pools, lending markets, vaults). Supports historical snapshots, enrichment with global state data, and cursor-based pagination."},{"name":"Transactions","description":"Look up position state changes by transaction hash or retrieve all transaction hashes for a user, grouped by opportunity. Useful for auditing, debugging, and building transaction-level analytics."},{"name":"Global States","description":"Fetch protocol-wide global state for an indexing target — either the full block-range series or the latest snapshot. Provides the shared context used to enrich and value individual positions."},{"name":"States Meta","description":"Discovery / metadata endpoints over the states-service — find storing_ids referencing a token, look up the indexed block / timestamp range for a storing_id, or fetch the raw processor config (set description) persisted for a storing_id."}],"paths":{"/v4/analytics/health/live":{"get":{"tags":["Health"],"summary":"Liveness probe — pod runtime snapshot","description":"Always returns HTTP 200. On the public API the body is `{ status: \"OK\" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`.","responses":{},"operationId":"getV4AnalyticsHealthLive"}},"/v4/analytics/health/ready":{"get":{"tags":["Health"],"summary":"Readiness probe — pod runtime snapshot","description":"Always returns HTTP 200. On the public API the body is `{ status: \"OK\" }`. On the internal instance it carries the pod's rolling 60s runtime window — mean cores against the cgroup quota, responses served, RSS against the memory limit, worst event-loop drift. Nothing here fails the probe yet: the thresholds that would identify a GC-bound pod are not calibrated against production traffic, so this ships as telemetry first. Every window is also logged as `[RUNTIME] window`.","responses":{"200":{"description":"Response for status 200","anyOf":[{"type":"object","properties":{"cpuCores":{"type":"number"},"cpuQuotaCores":{"type":"number"},"cpuLimitCores":{"type":"number"},"cpuLimitBasis":{"anyOf":[{"const":"quota","type":"string"},{"const":"host","type":"string"}]},"cpuRatio":{"type":"number"},"requestsInWindow":{"type":"number"},"rssBytes":{"type":"number"},"rssRatio":{"type":"number"},"maxEventLoopLagMs":{"type":"number"},"windowSeconds":{"type":"number"}},"required":["cpuCores","requestsInWindow","rssBytes","maxEventLoopLagMs","windowSeconds"]},{"type":"object","properties":{"status":{"const":"OK","type":"string"}},"required":["status"]}],"content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"cpuCores":{"type":"number"},"cpuQuotaCores":{"type":"number"},"cpuLimitCores":{"type":"number"},"cpuLimitBasis":{"anyOf":[{"const":"quota","type":"string"},{"const":"host","type":"string"}]},"cpuRatio":{"type":"number"},"requestsInWindow":{"type":"number"},"rssBytes":{"type":"number"},"rssRatio":{"type":"number"},"maxEventLoopLagMs":{"type":"number"},"windowSeconds":{"type":"number"}},"required":["cpuCores","requestsInWindow","rssBytes","maxEventLoopLagMs","windowSeconds"]},{"type":"object","properties":{"status":{"const":"OK","type":"string"}},"required":["status"]}]}}}},"503":{"description":"Response for status 503","properties":{"shuttingDown":{"const":true,"type":"boolean"}},"required":["shuttingDown"],"content":{"application/json":{"schema":{"type":"object","properties":{"shuttingDown":{"const":true,"type":"boolean"}},"required":["shuttingDown"]}}}}},"operationId":"getV4AnalyticsHealthReady"}},"/v4/analytics/positions/{userAddress}/{chainId}":{"get":{"tags":["Positions"],"summary":"Get user position history","description":"Fetch historical position data for a user across opportunities.\n\n**Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access.\n\n**Response:**\n- `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId.\n- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.\n- `globalStates` (populated per snapshot when `includeDisplay=true`): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing.\n- `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor.\n- `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key.\n- `pagination.positionCount`: Number of positions returned\n- `pagination.totalHistorySnapshots`: Total history snapshots (when > 0)\n- `pagination.pageInfo`: Cursor-based pagination info (when applicable)\n\n**PnL (`includePnl=true`):** adds a running `pnl` block to every snapshot's `display` — cost basis, per-event cashflow, PnL, ROI, all USD at each event's own daily-close price — A position's own PnL is its `latest.display.pnl`; there is no duplicate block on the position.\n\nEvery `history` row is one capital event, so a row's state delta is the cashflow and everything else is PnL. PnL therefore needs the **complete** series: the flag implies `includeDisplay` and `includeHistory`, and **disregards `first`/`after`**, since `first` pages rows newest-first portfolio-wide and drops the oldest writes — exactly the cost basis. The walk is row-capped; if it stops early, or a `minTimestamp` anchor opens the series, the block reports `basis: \"truncated\"`. Scope large portfolios with `identifier` or `indexingIds`.\n\n`summary` carries the request's aggregates: `total` plus `byIdentifier` nested chainId → identifier, mirroring `data`. ROI is recomputed from the aggregate, never averaged, and divides by **mean capital at risk** (the basis time-weighted over the time it was funded) — so it survives a position closing, where the net basis goes negative, and it is not inflated by capital recycled through the position. The per-position means are summed, which is exact only where the positions' windows coincide. A borrow's cost basis is capital *received*, so `costBasisUsd` subtracts it and reports net capital committed. Always read `positionsIncluded`/`positionsExcluded` — PnL cannot be computed for every position (unsupported `stateType`, unpriced token), and a total that omits some says so there rather than looking complete.\n\nExcluded: protocol fees (CLAMM state carries liquidity only) and Merkl incentives (not position states).\n\n**Examples:**\n- All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1`\n- With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true`\n- With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true`\n- With PnL: `GET /v4/analytics/positions/0xUserAddress/1?includePnl=true&identifier=0xPoolAddress`\n- Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap`\n- Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress`","parameters":[{"name":"userAddress","in":"path","required":true,"schema":{"description":"User wallet address to query","type":"string"}},{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to filter by (optional)","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to filter by (optional)","type":"number"}]}},{"name":"includeHistory","in":"query","required":true,"schema":{"default":false,"description":"Include per-position history snapshots","type":"boolean"}},{"name":"includeDisplay","in":"query","required":true,"schema":{"default":false,"description":"Decorate each snapshot with a self-describing `display` block: one entry per token backing its amount(s) (id/symbol/decimals/raw amount) plus `valueUsd` per entry and a snapshot total — so callers stop re-deriving, per `stateType`, which token each amount belongs to. Always includes `valueUsd` (resolves prices on its own). Runs global-state enrichment first (CLAMM/rebased amounts only exist after it).","type":"boolean"}},{"name":"includePnl","in":"query","required":true,"schema":{"default":false,"description":"Add a running `pnl` block (cost basis, per-event cashflow, PnL, ROI) to every snapshot's `display`, plus a top-level `summary`. A position's own PnL is `latest.display.pnl`. Implies `includeDisplay` and `includeHistory`, and **overrides pagination**: `first`/`after` are disregarded and the complete series is returned (row-capped), since a partial series understates the cost basis. Scope large portfolios with `identifier` or `indexingIds`. Each block's `basis` says whether the series reached inception.","type":"boolean"}},{"name":"minTimestamp","in":"query","required":false,"schema":{"description":"History lower bound (unix seconds). Only applies when includeHistory=true: returns all snapshots strictly after this timestamp PLUS the closest snapshot at-or-before it per position — the anchor that represents the state as of minTimestamp. The anchor row is reported as-of minTimestamp: blockTimestamp=minTimestamp and blockNumber=the on-chain block live at that time (resolved via BlockService); the state value is the last write's value. Positions with no anchor and no post-minTimestamp activity don't appear. Disregarded when includeHistory=false (the latest snapshot is always returned).","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"History lower bound (unix seconds). Only applies when includeHistory=true: returns all snapshots strictly after this timestamp PLUS the closest snapshot at-or-before it per position — the anchor that represents the state as of minTimestamp. The anchor row is reported as-of minTimestamp: blockTimestamp=minTimestamp and blockNumber=the on-chain block live at that time (resolved via BlockService); the state value is the last write's value. Positions with no anchor and no post-minTimestamp activity don't appear. Disregarded when includeHistory=false (the latest snapshot is always returned).","type":"number"}]}},{"name":"daily","in":"query","required":true,"schema":{"default":false,"description":"Bucket history snapshots daily","type":"boolean"}},{"name":"first","in":"query","required":false,"schema":{"minimum":1,"maximum":1000,"default":1000,"description":"Number of historical snapshots to return","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":1000,"default":1000,"description":"Number of historical snapshots to return","type":"number"}]}},{"name":"protocol","in":"query","required":false,"schema":{"description":"Filter positions by protocol name (e.g., 'uniswap'). Case-insensitive partial match","type":"string"}},{"name":"identifier","in":"query","required":false,"schema":{"description":"Filter to a single opportunity identifier (pool address, market id, etc.). Case-insensitive exact match. Resolved to its indexingIds and filtered at the DB level in states-service, so pagination and pricing apply to the matched opportunity only, not the whole portfolio. Combines with `indexingIds` as an intersection.","type":"string"}},{"name":"indexingIds","in":"query","required":false,"schema":{"description":"Keep only these indexingIds (comma-separated or repeated). Filtered at the DB level in states-service (on indexing_id), so callers scoped to a few groups (e.g. the indexingIds backing one dashboard's opportunities) fetch and price only those, not the whole portfolio.","type":"array","items":{"type":"string"}}},{"name":"after","in":"query","required":false,"schema":{"description":"Cursor for pagination","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"description":"Wrapped positions keyed by chainId, then identifier, then indexingId","type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"chainId":{"description":"Chain id shared by every position in this group.","type":"number"},"identifier":{"description":"Opportunity identifier (pool/token/market id) shared by the group.","type":"string"},"protocol":{"type":"string"},"action":{"description":"Action label from the state description (e.g. 'supply', 'lend', 'lp')","type":"string"},"stateType":{"description":"Position StateType, lowercased (e.g. 'balance', 'clamm_position').","type":"string"},"debt":{"description":"Present and `true` when this group's amounts are owed, not held — a borrow. Sum values across groups by subtracting these. Group-level: it follows from the processor, so every position and snapshot under it shares it.","type":"boolean"},"globalStates":{"description":"Descriptors of the global series enriching this group's positions. Keys are the engine's `GlobalSeriesKey` (`pool`, `multiplier`, `totalSupply`, `epoch`, ...). The value applied to each snapshot is on that snapshot's own `globalStates` (the row at-or-before its timestamp).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"type":{"description":"StateType of this global series, lowercased (e.g. 'clamm_pool', 'rebasing_multiplier').","type":"string"},"indexingId":{"description":"indexing_id under which the series is persisted — built by `globalSeriesStoringId(positionGroupIndexingId, globalSeriesKey)`, e.g. `<indexingId>:pool`. The global series key is the map key (and matches the engine's `GlobalSeriesKey`).","type":"string"}},"required":["type","indexingId"]}}},"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address","type":"string"},"address":{"description":"Token contract address","type":"string"},"symbol":{"description":"Token symbol (empty if unresolved)","type":"string"},"decimals":{"description":"Token decimals (0 if unresolved)","type":"number"},"side":{"description":"Which leg of a two-sided position this token denominates ('borrow'). Absent for the supply side and for one-sided positions.","type":"string"},"pairedLeg":{"description":"True when this token's leg declares two tokens (Fluid smart collateral / smart debt). The leg's single scalar balance is then not attributable to either token, so it gets no `display` entry.","type":"boolean"}},"required":["id","address","symbol","decimals"]}},"positions":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"user":{"type":"string"},"params":{"description":"Position-invariant fields, emitted once per position instead of on every snapshot. CLAMM: `tickLower`/`tickUpper`/`salt`. `poolId` and `tokenId` are not emitted: `poolId` is the positionGroup's `identifier`, `tokenId` the last segment of the positionId key.","type":"object","patternProperties":{"^(.*)$":{}}},"latest":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]},"history":{"type":"array","items":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]}},"historyStep":{"default":"afterUpdate","anyOf":[{"const":"afterUpdate","type":"string"},{"const":"hourly","type":"string"},{"const":"daily","type":"string"},{"const":"weekly","type":"string"}]},"historyStat":{"type":"object","properties":{"min":{"description":"Minimum scalar value across history snapshots","type":"string"},"max":{"description":"Maximum scalar value across history snapshots","type":"string"},"timeWeightedMean":{"description":"Time-weighted mean scalar value across history snapshots (each value weighted by its duration until the next snapshot; integer division)","type":"string"}},"required":["min","max","timeWeightedMean"]},"capital":{"description":"Stretches where this position had capital at risk, present with `includePnl`. Merge them across positions on one timeline to get a subset's ROI denominator — summing per-position means counts capital rotated from one position into the next once for each holder.","type":"array","items":{"type":"object","properties":{"from":{"description":"Unix seconds the stretch opens at — the write that set this basis.","type":"number"},"to":{"description":"Unix seconds the stretch closes at — the next write, or the checkpoint.","type":"number"},"capitalUsd":{"description":"Cost basis in force over the stretch, always positive.","type":"number"}},"required":["from","to","capitalUsd"]}}},"required":["user","latest"]}}}},"required":["chainId","identifier","positions"]}}}}}}},"summary":{"description":"PnL aggregates. Present only when `includePnl=true`.","type":"object","properties":{"total":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"positionsIncluded":{"description":"Positions whose figures are in these totals.","type":"number"},"positionsExcluded":{"description":"Positions left out (no cashflow source for the stateType, or nothing priced).","type":"number"}},"required":["basis","positionsIncluded","positionsExcluded"]},"byIdentifier":{"description":"Keyed by chainId, then identifier — the same nesting as `data`.","type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"positionsIncluded":{"description":"Positions whose figures are in these totals.","type":"number"},"positionsExcluded":{"description":"Positions left out (no cashflow source for the stateType, or nothing priced).","type":"number"}},"required":["basis","positionsIncluded","positionsExcluded"]}}}}}},"required":["total","byIdentifier"]},"pagination":{"type":"object","properties":{"positionCount":{"description":"Actual number of items returned","type":"number"},"totalHistorySnapshots":{"description":"Total number of history snapshots","type":"number"},"pageInfo":{"type":"object","properties":{"endCursor":{"description":"Cursor for the next page","type":"string"},"hasNextPage":{"description":"Whether more results are available — matches the transactions route","type":"boolean"},"hasMoreHistory":{"description":"Deprecated alias of `hasNextPage`, kept for existing consumers. Prefer `hasNextPage`.","type":"boolean"}},"required":["hasNextPage","hasMoreHistory"]}},"required":["positionCount"]}},"required":["data","pagination"]}}}}},"operationId":"getV4AnalyticsPositionsByUserAddressByChainId"}},"/v4/analytics/positions/{userAddress}":{"get":{"tags":["Positions"],"summary":"Get user position history","description":"Fetch historical position data for a user across opportunities.\n\n**Auth:** a valid `x-api-key` developer key, or an authenticated JWT identity (cookie or Bearer) — both grant the same read access.\n\n**Response:**\n- `data[chainId][identifier][indexingId]`: Wrapped positions grouped by chain ID → identifier → indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId.\n- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.\n- `globalStates` (populated per snapshot when `includeDisplay=true`): at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`). The value applied to a snapshot lives on that snapshot's own `globalStates[key] = { blockTimestamp, value }` — the series row at-or-before the snapshot's timestamp (carry-forward) — so each snapshot (`latest` + each `history` entry) is self-describing.\n- `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor.\n- `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key.\n- `pagination.positionCount`: Number of positions returned\n- `pagination.totalHistorySnapshots`: Total history snapshots (when > 0)\n- `pagination.pageInfo`: Cursor-based pagination info (when applicable)\n\n**PnL (`includePnl=true`):** adds a running `pnl` block to every snapshot's `display` — cost basis, per-event cashflow, PnL, ROI, all USD at each event's own daily-close price — A position's own PnL is its `latest.display.pnl`; there is no duplicate block on the position.\n\nEvery `history` row is one capital event, so a row's state delta is the cashflow and everything else is PnL. PnL therefore needs the **complete** series: the flag implies `includeDisplay` and `includeHistory`, and **disregards `first`/`after`**, since `first` pages rows newest-first portfolio-wide and drops the oldest writes — exactly the cost basis. The walk is row-capped; if it stops early, or a `minTimestamp` anchor opens the series, the block reports `basis: \"truncated\"`. Scope large portfolios with `identifier` or `indexingIds`.\n\n`summary` carries the request's aggregates: `total` plus `byIdentifier` nested chainId → identifier, mirroring `data`. ROI is recomputed from the aggregate, never averaged, and divides by **mean capital at risk** (the basis time-weighted over the time it was funded) — so it survives a position closing, where the net basis goes negative, and it is not inflated by capital recycled through the position. The per-position means are summed, which is exact only where the positions' windows coincide. A borrow's cost basis is capital *received*, so `costBasisUsd` subtracts it and reports net capital committed. Always read `positionsIncluded`/`positionsExcluded` — PnL cannot be computed for every position (unsupported `stateType`, unpriced token), and a total that omits some says so there rather than looking complete.\n\nExcluded: protocol fees (CLAMM state carries liquidity only) and Merkl incentives (not position states).\n\n**Examples:**\n- All positions on chain 1: `GET /v4/analytics/positions/0xUserAddress/1`\n- With history: `GET /v4/analytics/positions/0xUserAddress/1?includeHistory=true`\n- With USD display values: `GET /v4/analytics/positions/0xUserAddress/1?includeDisplay=true`\n- With PnL: `GET /v4/analytics/positions/0xUserAddress/1?includePnl=true&identifier=0xPoolAddress`\n- Filter by protocol: `GET /v4/analytics/positions/0xUserAddress/1?protocol=uniswap`\n- Filter by identifier: `GET /v4/analytics/positions/0xUserAddress/1?identifier=0xPoolAddress`","parameters":[{"name":"userAddress","in":"path","required":true,"schema":{"description":"User wallet address to query","type":"string"}},{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to filter by (optional)","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to filter by (optional)","type":"number"}]}},{"name":"includeHistory","in":"query","required":true,"schema":{"default":false,"description":"Include per-position history snapshots","type":"boolean"}},{"name":"includeDisplay","in":"query","required":true,"schema":{"default":false,"description":"Decorate each snapshot with a self-describing `display` block: one entry per token backing its amount(s) (id/symbol/decimals/raw amount) plus `valueUsd` per entry and a snapshot total — so callers stop re-deriving, per `stateType`, which token each amount belongs to. Always includes `valueUsd` (resolves prices on its own). Runs global-state enrichment first (CLAMM/rebased amounts only exist after it).","type":"boolean"}},{"name":"includePnl","in":"query","required":true,"schema":{"default":false,"description":"Add a running `pnl` block (cost basis, per-event cashflow, PnL, ROI) to every snapshot's `display`, plus a top-level `summary`. A position's own PnL is `latest.display.pnl`. Implies `includeDisplay` and `includeHistory`, and **overrides pagination**: `first`/`after` are disregarded and the complete series is returned (row-capped), since a partial series understates the cost basis. Scope large portfolios with `identifier` or `indexingIds`. Each block's `basis` says whether the series reached inception.","type":"boolean"}},{"name":"minTimestamp","in":"query","required":false,"schema":{"description":"History lower bound (unix seconds). Only applies when includeHistory=true: returns all snapshots strictly after this timestamp PLUS the closest snapshot at-or-before it per position — the anchor that represents the state as of minTimestamp. The anchor row is reported as-of minTimestamp: blockTimestamp=minTimestamp and blockNumber=the on-chain block live at that time (resolved via BlockService); the state value is the last write's value. Positions with no anchor and no post-minTimestamp activity don't appear. Disregarded when includeHistory=false (the latest snapshot is always returned).","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"History lower bound (unix seconds). Only applies when includeHistory=true: returns all snapshots strictly after this timestamp PLUS the closest snapshot at-or-before it per position — the anchor that represents the state as of minTimestamp. The anchor row is reported as-of minTimestamp: blockTimestamp=minTimestamp and blockNumber=the on-chain block live at that time (resolved via BlockService); the state value is the last write's value. Positions with no anchor and no post-minTimestamp activity don't appear. Disregarded when includeHistory=false (the latest snapshot is always returned).","type":"number"}]}},{"name":"daily","in":"query","required":true,"schema":{"default":false,"description":"Bucket history snapshots daily","type":"boolean"}},{"name":"first","in":"query","required":false,"schema":{"minimum":1,"maximum":1000,"default":1000,"description":"Number of historical snapshots to return","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":1000,"default":1000,"description":"Number of historical snapshots to return","type":"number"}]}},{"name":"protocol","in":"query","required":false,"schema":{"description":"Filter positions by protocol name (e.g., 'uniswap'). Case-insensitive partial match","type":"string"}},{"name":"identifier","in":"query","required":false,"schema":{"description":"Filter to a single opportunity identifier (pool address, market id, etc.). Case-insensitive exact match. Resolved to its indexingIds and filtered at the DB level in states-service, so pagination and pricing apply to the matched opportunity only, not the whole portfolio. Combines with `indexingIds` as an intersection.","type":"string"}},{"name":"indexingIds","in":"query","required":false,"schema":{"description":"Keep only these indexingIds (comma-separated or repeated). Filtered at the DB level in states-service (on indexing_id), so callers scoped to a few groups (e.g. the indexingIds backing one dashboard's opportunities) fetch and price only those, not the whole portfolio.","type":"array","items":{"type":"string"}}},{"name":"after","in":"query","required":false,"schema":{"description":"Cursor for pagination","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"description":"Wrapped positions keyed by chainId, then identifier, then indexingId","type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"chainId":{"description":"Chain id shared by every position in this group.","type":"number"},"identifier":{"description":"Opportunity identifier (pool/token/market id) shared by the group.","type":"string"},"protocol":{"type":"string"},"action":{"description":"Action label from the state description (e.g. 'supply', 'lend', 'lp')","type":"string"},"stateType":{"description":"Position StateType, lowercased (e.g. 'balance', 'clamm_position').","type":"string"},"debt":{"description":"Present and `true` when this group's amounts are owed, not held — a borrow. Sum values across groups by subtracting these. Group-level: it follows from the processor, so every position and snapshot under it shares it.","type":"boolean"},"globalStates":{"description":"Descriptors of the global series enriching this group's positions. Keys are the engine's `GlobalSeriesKey` (`pool`, `multiplier`, `totalSupply`, `epoch`, ...). The value applied to each snapshot is on that snapshot's own `globalStates` (the row at-or-before its timestamp).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"type":{"description":"StateType of this global series, lowercased (e.g. 'clamm_pool', 'rebasing_multiplier').","type":"string"},"indexingId":{"description":"indexing_id under which the series is persisted — built by `globalSeriesStoringId(positionGroupIndexingId, globalSeriesKey)`, e.g. `<indexingId>:pool`. The global series key is the map key (and matches the engine's `GlobalSeriesKey`).","type":"string"}},"required":["type","indexingId"]}}},"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address","type":"string"},"address":{"description":"Token contract address","type":"string"},"symbol":{"description":"Token symbol (empty if unresolved)","type":"string"},"decimals":{"description":"Token decimals (0 if unresolved)","type":"number"},"side":{"description":"Which leg of a two-sided position this token denominates ('borrow'). Absent for the supply side and for one-sided positions.","type":"string"},"pairedLeg":{"description":"True when this token's leg declares two tokens (Fluid smart collateral / smart debt). The leg's single scalar balance is then not attributable to either token, so it gets no `display` entry.","type":"boolean"}},"required":["id","address","symbol","decimals"]}},"positions":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"user":{"type":"string"},"params":{"description":"Position-invariant fields, emitted once per position instead of on every snapshot. CLAMM: `tickLower`/`tickUpper`/`salt`. `poolId` and `tokenId` are not emitted: `poolId` is the positionGroup's `identifier`, `tokenId` the last segment of the positionId key.","type":"object","patternProperties":{"^(.*)$":{}}},"latest":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]},"history":{"type":"array","items":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]}},"historyStep":{"default":"afterUpdate","anyOf":[{"const":"afterUpdate","type":"string"},{"const":"hourly","type":"string"},{"const":"daily","type":"string"},{"const":"weekly","type":"string"}]},"historyStat":{"type":"object","properties":{"min":{"description":"Minimum scalar value across history snapshots","type":"string"},"max":{"description":"Maximum scalar value across history snapshots","type":"string"},"timeWeightedMean":{"description":"Time-weighted mean scalar value across history snapshots (each value weighted by its duration until the next snapshot; integer division)","type":"string"}},"required":["min","max","timeWeightedMean"]},"capital":{"description":"Stretches where this position had capital at risk, present with `includePnl`. Merge them across positions on one timeline to get a subset's ROI denominator — summing per-position means counts capital rotated from one position into the next once for each holder.","type":"array","items":{"type":"object","properties":{"from":{"description":"Unix seconds the stretch opens at — the write that set this basis.","type":"number"},"to":{"description":"Unix seconds the stretch closes at — the next write, or the checkpoint.","type":"number"},"capitalUsd":{"description":"Cost basis in force over the stretch, always positive.","type":"number"}},"required":["from","to","capitalUsd"]}}},"required":["user","latest"]}}}},"required":["chainId","identifier","positions"]}}}}}}},"summary":{"description":"PnL aggregates. Present only when `includePnl=true`.","type":"object","properties":{"total":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"positionsIncluded":{"description":"Positions whose figures are in these totals.","type":"number"},"positionsExcluded":{"description":"Positions left out (no cashflow source for the stateType, or nothing priced).","type":"number"}},"required":["basis","positionsIncluded","positionsExcluded"]},"byIdentifier":{"description":"Keyed by chainId, then identifier — the same nesting as `data`.","type":"object","patternProperties":{"^(.*)$":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"positionsIncluded":{"description":"Positions whose figures are in these totals.","type":"number"},"positionsExcluded":{"description":"Positions left out (no cashflow source for the stateType, or nothing priced).","type":"number"}},"required":["basis","positionsIncluded","positionsExcluded"]}}}}}},"required":["total","byIdentifier"]},"pagination":{"type":"object","properties":{"positionCount":{"description":"Actual number of items returned","type":"number"},"totalHistorySnapshots":{"description":"Total number of history snapshots","type":"number"},"pageInfo":{"type":"object","properties":{"endCursor":{"description":"Cursor for the next page","type":"string"},"hasNextPage":{"description":"Whether more results are available — matches the transactions route","type":"boolean"},"hasMoreHistory":{"description":"Deprecated alias of `hasNextPage`, kept for existing consumers. Prefer `hasNextPage`.","type":"boolean"}},"required":["hasNextPage","hasMoreHistory"]}},"required":["positionCount"]}},"required":["data","pagination"]}}}}},"operationId":"getV4AnalyticsPositionsByUserAddress"}},"/v4/analytics/positions/by-identifier/{chainId}/{identifier}":{"get":{"tags":["Positions"],"summary":"Get positions by opportunity identifier","description":"Fetch the latest position for every user in a given opportunity.\n\n**Response:**\n- `data[indexingId]`: Wrapped positions keyed by indexingId. Each positionGroup carries shared metadata (`protocol`, `action`, `stateType`, `globalStates`, `tokens`) and a `positions` map keyed by positionId. Each position entry carries its `user` field; multiple users may share the same indexingId positionGroup.\n- `latest` is reported **as of the indexing checkpoint**: its `(blockNumber, blockTimestamp)` are advanced to how far indexing has completed (state unchanged through there), never beyond it — so the state is never presented as more current than what's indexed.\n- `globalStates`: at the positionGroup level it holds series **descriptors** only (`type`, `indexingId`) — fetch the values from `GET /v4/analytics/global-states`. This route returns raw states; it runs no global-state enrichment.\n- `display.unpricedReason` (when `valueUsd` is absent): which step gave up — `no_price` (the token mapped but has no daily close at or before that day), `no_token_mapped` (no declared token denominates the amount, e.g. a market-keyed identifier), `unresolved_amount` (global-state enrichment produced none), `unsupported_state` (no valuation rule for the `stateType`). A price gap and an unsupported shape both read as \"no value\" without it.\n- `debt`: present and `true` when the group's amounts are owed, not held (a borrow) — sum values across groups by subtracting these. Set once per group: it follows from the processor.\n- `positions[positionId].params`: position-invariant fields hoisted out of the snapshots, emitted **once per position**. Shape follows `stateType`; CLAMM carries `tickLower`/`tickUpper` (and `salt` when the persisted state has one), which is why they are no longer inside each snapshot's `state`. `poolId` and `tokenId` are not emitted at all: `poolId` is the positionGroup's `identifier`, and `tokenId` is the last segment of the positionId key.\n- `pagination.positionCount`: Total number of position entries returned\n- `pagination.recipientsCount`: Number of distinct user addresses across all positions\n\n**Examples:**\n- Latest positions: `GET /v4/analytics/positions/by-identifier/1/0xPoolAddress`","parameters":[{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to query","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to query","type":"number"}]}},{"name":"identifier","in":"path","required":true,"schema":{"description":"Pool address, token address, market ID, or any opportunity identifier","type":"string"}},{"name":"includeDisplay","in":"query","required":true,"schema":{"default":false,"description":"Decorate each snapshot with the same self-describing `display` block as the by-user route (per-token id/symbol/decimals/amount plus `valueUsd`). Enriches the states it needs on its own.","type":"boolean"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"description":"Wrapped positions keyed by indexingId","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"chainId":{"description":"Chain id shared by every position in this group.","type":"number"},"identifier":{"description":"Opportunity identifier (pool/token/market id) shared by the group.","type":"string"},"protocol":{"type":"string"},"action":{"description":"Action label from the state description (e.g. 'supply', 'lend', 'lp')","type":"string"},"stateType":{"description":"Position StateType, lowercased (e.g. 'balance', 'clamm_position').","type":"string"},"debt":{"description":"Present and `true` when this group's amounts are owed, not held — a borrow. Sum values across groups by subtracting these. Group-level: it follows from the processor, so every position and snapshot under it shares it.","type":"boolean"},"globalStates":{"description":"Descriptors of the global series enriching this group's positions. Keys are the engine's `GlobalSeriesKey` (`pool`, `multiplier`, `totalSupply`, `epoch`, ...). The value applied to each snapshot is on that snapshot's own `globalStates` (the row at-or-before its timestamp).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"type":{"description":"StateType of this global series, lowercased (e.g. 'clamm_pool', 'rebasing_multiplier').","type":"string"},"indexingId":{"description":"indexing_id under which the series is persisted — built by `globalSeriesStoringId(positionGroupIndexingId, globalSeriesKey)`, e.g. `<indexingId>:pool`. The global series key is the map key (and matches the engine's `GlobalSeriesKey`).","type":"string"}},"required":["type","indexingId"]}}},"tokens":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address","type":"string"},"address":{"description":"Token contract address","type":"string"},"symbol":{"description":"Token symbol (empty if unresolved)","type":"string"},"decimals":{"description":"Token decimals (0 if unresolved)","type":"number"},"side":{"description":"Which leg of a two-sided position this token denominates ('borrow'). Absent for the supply side and for one-sided positions.","type":"string"},"pairedLeg":{"description":"True when this token's leg declares two tokens (Fluid smart collateral / smart debt). The leg's single scalar balance is then not attributable to either token, so it gets no `display` entry.","type":"boolean"}},"required":["id","address","symbol","decimals"]}},"positions":{"type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"user":{"type":"string"},"params":{"description":"Position-invariant fields, emitted once per position instead of on every snapshot. CLAMM: `tickLower`/`tickUpper`/`salt`. `poolId` and `tokenId` are not emitted: `poolId` is the positionGroup's `identifier`, `tokenId` the last segment of the positionId key.","type":"object","patternProperties":{"^(.*)$":{}}},"latest":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]},"history":{"type":"array","items":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"state":{},"globalStates":{"description":"Suffix-keyed global-state value(s) applied to enrich THIS snapshot; keys match the group's `globalStates`. Each value is the series row at-or-before this snapshot's timestamp (carry-forward).","type":"object","patternProperties":{"^(.*)$":{"type":"object","properties":{"blockTimestamp":{"description":"Block timestamp (seconds) of the applied global-state row.","type":"number"},"value":{"description":"Global-state payload applied to this snapshot (same `state` vocabulary).","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["blockTimestamp","value"]}}},"display":{"type":"object","properties":{"unpricedReason":{"anyOf":[{"const":"no_price","type":"string"},{"const":"no_token_mapped","type":"string"},{"const":"unresolved_amount","type":"string"},{"const":"unsupported_state","type":"string"}]},"valueUsd":{"description":"Total USD value of the snapshot (sum of every entry's `valueUsd`). Present only when every entry resolved a price (all-or-nothing).","type":"number"},"amounts":{"description":"One entry per token backing this snapshot's amount(s); empty if the snapshot yields none.","type":"array","items":{"type":"object","properties":{"id":{"description":"Coingecko-style coin ID: chain:address — matches an entry in the positionGroup's `tokens`","type":"string"},"symbol":{"description":"Token symbol. For an unresolved LP/pool token backing ≥2 underlyings, synthesized as the underlyings' symbols joined by '-' (e.g. 'wnWMON-wnSHMON'); otherwise the raw (possibly empty) symbol.","type":"string"},"decimals":{"description":"Token decimals (runtime-resolved; 0 if unresolved)","type":"number"},"amount":{"description":"Raw token amount (wei), as a string","type":"string"},"valueUsd":{"description":"USD value at the snapshot's daily-close price; present only when that token priced.","type":"number"}},"required":["id","symbol","decimals","amount"]}},"pnl":{"type":"object","properties":{"costBasisUsd":{"description":"Net cashflow through the position, in its own direction: capital invested for a supply, principal outstanding for a borrow. Goes negative once outflows exceed inflows — on a closed position that surplus is the profit, so `pnlUsd` is exactly its negation. A borrow's basis therefore stays positive, and the negation the summary applies to it is what makes its total read as net capital committed.","type":"number"},"pnlUsd":{"description":"Current value minus `costBasisUsd`.","type":"number"},"roi":{"description":"`pnlUsd` over **mean capital at risk**: the cost basis integrated over the time it was positive, divided by that time. Not gross inflows, which count recycled capital once per round trip; not `costBasisUsd`, which inverts the ratio once a position closes. Idle stretches are excluded, so $100 held for a day then flat for a year is a return on $100. Percentage (15.5 = +15.5%). Absent when no capital was ever at risk. A mean, so it ignores when in the window the flows landed — not an IRR — and it does not net leverage off. Covers only the stretch since the basis last restarted: an unreadable row discards the basis it built, so on a restarted series — including one still reporting `complete` — this is the return over the post-restart tail. A full exit is not a restart.","type":"number"},"basis":{"anyOf":[{"const":"complete","type":"string"},{"const":"truncated","type":"string"},{"const":"unpriced","type":"string"}]},"cashflowUsd":{"description":"This snapshot's own cashflow: positive for capital in, negative for capital out. Always 0 on `latest` and on a `minTimestamp` anchor row — neither is a capital event.","type":"number"}},"required":["basis"]}},"required":["amounts"]}},"required":["blockNumber","blockTimestamp","state"]}},"historyStep":{"default":"afterUpdate","anyOf":[{"const":"afterUpdate","type":"string"},{"const":"hourly","type":"string"},{"const":"daily","type":"string"},{"const":"weekly","type":"string"}]},"historyStat":{"type":"object","properties":{"min":{"description":"Minimum scalar value across history snapshots","type":"string"},"max":{"description":"Maximum scalar value across history snapshots","type":"string"},"timeWeightedMean":{"description":"Time-weighted mean scalar value across history snapshots (each value weighted by its duration until the next snapshot; integer division)","type":"string"}},"required":["min","max","timeWeightedMean"]},"capital":{"description":"Stretches where this position had capital at risk, present with `includePnl`. Merge them across positions on one timeline to get a subset's ROI denominator — summing per-position means counts capital rotated from one position into the next once for each holder.","type":"array","items":{"type":"object","properties":{"from":{"description":"Unix seconds the stretch opens at — the write that set this basis.","type":"number"},"to":{"description":"Unix seconds the stretch closes at — the next write, or the checkpoint.","type":"number"},"capitalUsd":{"description":"Cost basis in force over the stretch, always positive.","type":"number"}},"required":["from","to","capitalUsd"]}}},"required":["user","latest"]}}}},"required":["chainId","identifier","positions"]}}},"pagination":{"type":"object","properties":{"positionCount":{"description":"Total number of position entries returned","type":"number"},"recipientsCount":{"description":"Number of distinct user addresses in the response","type":"number"}},"required":["positionCount","recipientsCount"]}},"required":["data","pagination"],"additionalProperties":false}}}}},"operationId":"getV4AnalyticsPositionsBy-identifierByChainIdByIdentifier"}},"/v4/analytics/positions/at/{storingId}":{"get":{"tags":["Positions"],"summary":"Get closest position states at a timestamp","description":"Return the closest position state(s) to a Unix timestamp for a storingId.\n\n`order=DESC` (default) returns the last state at or before the timestamp; `order=ASC` returns the first state at or after it.\n\nEach entry mirrors the gRPC `StateV2` shape, so the response carries the raw `state` blob unmodified.\n\n**Examples:**\n- Latest state ≤ T:    `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620`\n- First state ≥ T:     `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620&order=ASC`\n- One specific user:   `GET /v4/analytics/positions/at/<storingId>?timestamp=1748635620&positionKey=0xUser`\n","parameters":[{"name":"storingId","in":"path","required":true,"schema":{"description":"The storing/indexing id","type":"string"}},{"name":"timestamp","in":"query","required":true,"schema":{"description":"Target unix timestamp in seconds","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Target unix timestamp in seconds","type":"number"}]}},{"name":"order","in":"query","required":true,"schema":{"default":"DESC","description":"ASC = first state at or after T; DESC = last state at or before T","anyOf":[{"const":"ASC","type":"string"},{"const":"DESC","type":"string"}]}},{"name":"positionKey","in":"query","required":false,"schema":{"description":"Optional position key filter (empty = no filter)","type":"string"}},{"name":"positionGranularityKey","in":"query","required":false,"schema":{"description":"Optional sub-position filter, e.g. tokenId (empty = no filter)","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"states":{"description":"Closest state(s) at or near the requested timestamp, one entry per position","type":"array","items":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"storingId":{"type":"string"},"positionKey":{"type":"string"},"owner":{"type":"string"},"positionGranularityKey":{"type":"string"},"state":{"type":"object","patternProperties":{"^(.*)$":{}}},"txHashes":{"type":"array","items":{"type":"string"}}},"required":["blockNumber","blockTimestamp","storingId","positionKey","owner","positionGranularityKey","state","txHashes"]}}},"required":["states"],"additionalProperties":false}}}}},"operationId":"getV4AnalyticsPositionsAtByStoringId"}},"/v4/analytics/transactions/{chainId}":{"get":{"tags":["Transactions"],"summary":"Get states by transaction hashes (V2)","description":"Retrieves position states associated with the given transaction hashes from the V2 position_states table. Returns enriched state data including protocol, action, and token metadata derived from state descriptions.","parameters":[{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to query","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to query","type":"number"}]}},{"name":"txHashes","in":"query","required":true,"schema":{"description":"Comma-separated transaction hashes (e.g., 0x123,0x456)","type":"string"}},{"name":"positionKeyFilter","in":"query","required":false,"schema":{"description":"Optional substring filter for position key","type":"string"}},{"name":"timestamps","in":"query","required":false,"schema":{"description":"Comma-separated Unix timestamps (seconds) aligned with txHashes","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"stateType":{"anyOf":[{"const":"position","type":"string"},{"const":"global","type":"string"}]},"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"chainId":{"type":"number"},"storingId":{"type":"string"},"positionKey":{"type":"string"},"positionGranularityKey":{"type":"string"},"owner":{"type":"string"},"protocol":{"type":"string"},"action":{"type":"string"},"identifier":{"type":"string"},"state":{},"txHashes":{"type":"array","items":{"type":"string"}}},"required":["stateType","blockNumber","blockTimestamp","chainId","storingId","protocol","action","identifier","state","txHashes"]}},"totalCount":{"description":"Total number of states found","type":"number"}},"required":["data","totalCount"]}}}}},"operationId":"getV4AnalyticsTransactionsByChainId"}},"/v4/analytics/transactions/by-user/{userAddress}/{chainId}":{"get":{"tags":["Transactions"],"summary":"Get tx hashes by user, grouped by indexing ID","description":"Returns all transaction hashes recorded for a given user address, grouped by indexing ID (opportunity). Supports optional chain filtering via the chainId path parameter. Results are cursor-paginated: pass `first` to set page size and `after` (from `pageInfo.endCursor`) to fetch the next page. Cursor encodes `(block_number, position_id)` — ordering is block_number DESC.","parameters":[{"name":"userAddress","in":"path","required":true,"schema":{"description":"User wallet address","type":"string"}},{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to filter by (required, must be non-zero)","minimum":1,"anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to filter by (required, must be non-zero)","minimum":1,"type":"number"}]}},{"name":"first","in":"query","required":false,"schema":{"minimum":1,"maximum":10000,"default":10000,"description":"Number of rows per page","anyOf":[{"format":"numeric","default":0,"type":"string"},{"minimum":1,"maximum":10000,"default":10000,"description":"Number of rows per page","type":"number"}]}},{"name":"after","in":"query","required":false,"schema":{"description":"Cursor for pagination (from previous pageInfo.endCursor)","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"description":"Tx hashes grouped by indexingId","type":"object","patternProperties":{"^(.*)$":{"type":"array","items":{"type":"string"}}}},"pagination":{"type":"object","properties":{"totalTxHashes":{"description":"Total number of tx hashes","type":"number"},"totalOpportunities":{"description":"Total number of distinct opportunities (indexingIds)","type":"number"},"pageInfo":{"type":"object","properties":{"startCursor":{"type":"string"},"endCursor":{"type":"string"},"hasNextPage":{"type":"boolean"}},"required":["startCursor","endCursor","hasNextPage"]}},"required":["totalTxHashes","totalOpportunities"]}},"required":["data","pagination"]}}}}},"operationId":"getV4AnalyticsTransactionsBy-userByUserAddressByChainId"}},"/v4/analytics/global-states/{indexingId}":{"get":{"tags":["Global States"],"parameters":[{"name":"indexingId","in":"path","required":true,"schema":{"type":"string"}},{"name":"fromBlock","in":"query","required":false,"schema":{"min":0,"anyOf":[{"format":"numeric","default":0,"type":"string"},{"min":0,"type":"number"}]}},{"name":"toBlock","in":"query","required":false,"schema":{"min":0,"anyOf":[{"format":"numeric","default":0,"type":"string"},{"min":0,"type":"number"}]}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"indexingId":{"type":"string"},"history":{"type":"array","items":{"type":"object","properties":{"blockNumber":{"type":"number"},"blockTimestamp":{"type":"number"},"value":{}},"required":["blockNumber","blockTimestamp","value"]}}},"required":["indexingId","history"]}}}}},"operationId":"getV4AnalyticsGlobal-statesByIndexingId"}},"/v4/analytics/global-states/{indexingId}/latest":{"get":{"tags":["Global States"],"parameters":[{"name":"indexingId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{},"operationId":"getV4AnalyticsGlobal-statesByIndexingIdLatest"}},"/v4/analytics/states-meta/find-by-token/{chainId}/{identifier}":{"get":{"tags":["States Meta"],"summary":"Find storing_ids by token / identifier","description":"Search the `state_set_descriptions` table for every storing_id whose `raw_config` references the given token contract or opportunity identifier.\n\nPass `chainId=0` to search across all chains.","parameters":[{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID to filter by (0 = all chains)","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID to filter by (0 = all chains)","type":"number"}]}},{"name":"identifier","in":"path","required":true,"schema":{"description":"Token contract address (or any opportunity identifier) to search for","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"storingIds":{"description":"List of storing_ids whose raw_config references the token/identifier","type":"array","items":{"type":"string"}}},"required":["storingIds"],"additionalProperties":false}}}}},"operationId":"getV4AnalyticsStates-metaFind-by-tokenByChainIdByIdentifier"}},"/v4/analytics/states-meta/{storingId}/block-range":{"get":{"tags":["States Meta"],"summary":"Get indexed block / timestamp range for a storing_id","description":"Return the min/max indexed `block_number` and `block_timestamp` for a storing_id. All four values are 0 when the storing_id has no indexed state yet.","parameters":[{"name":"storingId","in":"path","required":true,"schema":{"description":"The storing/indexing id","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"storingId":{"type":"string"},"minBlockNumber":{"description":"Lowest indexed block number (0 = no data)","type":"number"},"minBlockTimestamp":{"description":"Lowest indexed block timestamp in seconds (0 = no data)","type":"number"},"maxBlockNumber":{"description":"Highest indexed block number (0 = no data)","type":"number"},"maxBlockTimestamp":{"description":"Highest indexed block timestamp in seconds (0 = no data)","type":"number"}},"required":["storingId","minBlockNumber","minBlockTimestamp","maxBlockNumber","maxBlockTimestamp"]}}}}},"operationId":"getV4AnalyticsStates-metaByStoringIdBlock-range"}},"/v4/analytics/states-meta/{storingId}/setDescription":{"get":{"tags":["States Meta"],"summary":"Get raw processor config (set description) for a storing_id","description":"Return the raw processor config stored in the `state_set_description` table for the given storing_id.","parameters":[{"name":"storingId","in":"path","required":true,"schema":{"description":"The indexing (storing) id.","type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"object","properties":{"storingId":{"type":"string"},"rawConfig":{"description":"Raw processor config stored in the `state_set_description` table for this storingId.","type":"object","patternProperties":{"^(.*)$":{}}}},"required":["storingId","rawConfig"],"additionalProperties":false}}}}},"operationId":"getV4AnalyticsStates-metaByStoringIdSetDescription"}},"/v4/analytics/states-meta/address-tags/{chainId}":{"get":{"tags":["States Meta"],"summary":"Batch resolve address tags","description":"Resolve human-readable labels for up to 100 identifiers on a single chain.\n\n`identifier` may be any resolvable value: owner address, pool address, market id, bytes32 poolId, etc.\n\nMisses return `addressTag: null` — partial resolution is normal for plain EOAs.\nInput order is preserved in the response.\n\n**Example:**\n```\nGET /v4/analytics/states-meta/address-tags/8453?identifiers=0x7Ce5F356a10BBF528618259404543BB1e6412B69,0xRandomEOA\n```","parameters":[{"name":"chainId","in":"path","required":true,"schema":{"description":"Chain ID all identifiers are resolved against","anyOf":[{"format":"numeric","default":0,"type":"string"},{"description":"Chain ID all identifiers are resolved against","type":"number"}]}},{"name":"identifiers","in":"query","required":true,"schema":{"description":"Comma-separated identifiers (addresses / bytes32 ids), max 100","maxLength":8000,"type":"string"}}],"responses":{"200":{"description":"Response for status 200","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"identifier":{"type":"string"},"addressTag":{"anyOf":[{"type":"object","properties":{"label":{"description":"Human-readable label (e.g. ENS name, protocol contract role)","type":"string"},"protocolId":{"description":"Linked Protocol.id when known","type":"string"},"source":{"description":"Origin of the tag — `NODE > OPPORTUNITY` priority during merges","anyOf":[{"const":"NODE","type":"string"},{"const":"OPPORTUNITY","type":"string"}]}},"required":["label","source"]},{"type":"null"}]}},"required":["identifier","addressTag"]}}}}}},"operationId":"getV4AnalyticsStates-metaAddress-tagsByChainId"}}},"components":{"schemas":{}}}