Endpoints

Positions

Uniswap v4 liquidity positions on Ethereum (1), Arbitrum One (42161) and Avalanche (43114). Every filter and sort the dashboard uses is on the same endpoint — there is no reduced public subset.

GET /v1/positions — 1 unit

A ranked, filtered, paginated page of positions.

bash
curl -s "https://api.tickwise.xyz/v1/positions?chainId=1&sort=apr&limit=25" \
  -H "KC-APIKey: $TICKWISE_API_KEY"

Query parameters

Everything is optional. The defaults are not neutral — they are the visibility filters that keep spam and dust out of a ranking — so read the last three rows before concluding a position is missing.

ParameterDefaultMeaning
chainIdall chains1 or 43114. Omitting it means every enabled chain, merged into one ranking — not Ethereum. An unregistered id is a 400.
protocoluniswap-v4The only value served. uniswap-v3 is a 400 — see coverage.
sortaprOne of apr, fee_apr, roi, pnl, underlying_value, age, nft_id_asc, nft_id_desc. Direction is part of the key, not a separate parameter: everything is descending except age and nft_id_asc.
page11-based.
limit201–100. A larger value is a 400 rather than a silent clamp.
statusopen onlyactive, inactive or all — open, closed, or both. Unset behaves as active, because the older activeOnly flag it supersedes defaults to true; pass status=all to see closed positions.
rangeallin or out restricts to positions whose range currently contains the pool tick, or does not.
owner—A 0x-prefixed 40-hex EVM address, matched case-insensitively. Anything else is a 400 with a message naming the format.
poolId—Every position in one pool.
positionId—Exact match on a single id — the tokenId for an ERC-721 protocol. The other filters still apply, so a known id can still return nothing if it fails them.
minValueUsd0Floor on current underlying value.
minPoolTvl1000Floor on the pool’s TVL. Not zero — this is the one default that silently hides real positions in small pools.
includeRiskyfalseAdmits positions whose token has no corroborated on-chain market. Off by default because a ranking full of issuer-seeded spam pools is worthless.

Response

A positions array and a pagination object. Each row carries rank, identity (positionId, tokenId, chainId, protocol, owner, poolId), both token snapshots, the range (tickLower, tickUpper, currentTick, tickSpacing, fee, liquidity, isActive), and four nested groups: amounts (token-denominated deposits, withdrawals, collected and uncollected fees, gas), metrics (apr, feeApr, roi, pnlUsd, underlyingValueUsd, pendingFeesUsd, ageSeconds, inRange), poolStats and pricing.

json
"pagination": { "page": 1, "limit": 20, "total": 0, "totalPages": 1, "hasMore": false }

Page with hasMore, not totalPages. hasMore is always exact: each page reads one row past itself. The count behind total runs separately and is cached; when it is not ready within about two seconds, or when it reaches its cap of 25,000, the response carries "totalCapped": true and total / totalPages are a floor: the number of rows that page proves exist, not the size of the result set. Without totalCapped, total is exact, in all-chains mode too (the merge sums each shard’s own count). A loop that stops at totalPages from the first response can stop early. Loop until hasMore is false.

Two more flags, each present only when it applies. In all-chains mode (no chainId), a chain whose data is temporarily unavailable is left out rather than failing the whole request: the response adds "partial": true and "missingChains": […], the rows are the other chains’ merged order, total counts only those chains, and the request is not charged. Data older than the usual 45-second freshness is only ever served flagged: it carries "stale": true and dataAsOf, the time it was current. That happens when the database is too slow to answer (the last page we served comes back instead), when the database replica it was read from is running behind, and on a very deep page whose position in the list was counted more than 45 seconds ago. There is no page cap. Paging forward from one page to the next is cheap; the first visit to a very deep page can answer 503 with Retry-After while it is still being reached, and the retry continues from where that request got to.

Two fields on metrics exist to stop you reading a gap as a number. priceUnavailable marks a leg that could not be priced from any independent source, and unrealizableLeg marks one valued at zero because the claim exceeds the pool’s real exit liquidity. Neither is the same as a genuine zero.

The rate sorts — apr, fee_apr, roi — exclude closed and no-value positions rather than ranking them on a stale stored rate. pnl does not: realised PnL on a closed position is a real number and belongs in that ranking.

GET /v1/positions/{chainId}/{protocol}/{positionId} — 1 unit

One position in full, plus a transactions array the list endpoint does not carry: every mint, burn, collect and transfer with its token amounts, gas and per-transaction USD valuation.

bash
curl -s "https://api.tickwise.xyz/v1/positions/1/uniswap-v4/12345" \
  -H "KC-APIKey: $TICKWISE_API_KEY"

An id that does not exist on that chain and protocol is a 404, not an empty object — and an id from the other chain will 404 here, which is the usual cause.

The transaction list can come back incomplete, and says so rather than looking empty. "transactionsDegraded": true means the list could not be loaded in full in time. With it, "transactionsPartial": true means the array holds only the position’s newest transactions (up to 1,000) while the full history loads: ask again a few seconds later. "transactionsTruncated": true means the history is longer than 100,000 transactions and the newest are missing. A degraded answer is not charged. "stale": true with dataAsOf means the position itself was read from a database replica running more than 45 seconds behind.

GET /v1/positions/{chainId}/{protocol}/{positionId}/charts — 10 units

Time series for one position. ?range=30d|60d|90d|180d|all, defaulting to 30d; an unrecognised value falls back to 30d rather than erroring, because the alternative is letting a typo fan out unbounded queries against a paid upstream.

bash
curl -s "https://api.tickwise.xyz/v1/positions/1/uniswap-v4/12345/charts?range=90d" \
  -H "KC-APIKey: $TICKWISE_API_KEY"

The response carries poolPrice (daily OHLC plus both token prices), poolOverview (TVL, volume, fees, fees/TVL, both prices), liquidityDistribution (the tick histogram, with this position’s tickLower and tickUpper marked), positionSeries (PnL, asset value, APR, fee APR, divergence loss) and poolOverviewCurrent.

Three honest caveats. positionSeries is null when the series cannot be reconstructed, and null is not zero. poolOverview.divergence is always empty — the upstream schema does not carry it, and the key is present only so it can be filled without a breaking change. And this is the most expensive route we serve: it is a multi-query fan-out to an upstream we pay per call for, which is what the unit cost reflects.

Next

Pools — the same three shapes for pools. Units and limits — why a list costs more than a detail. Errors — every status code and whether retrying helps.