Changelog

What shipped

  1. 2026-10-04
    Breaking: sort=age is newest first everywhere, and the Closed tab ranks rate sorts by PnL

    sort=age now orders positions by creation time, newest first, for status=active, inactive and all alike; the Closed and All views used to return a different order from the Active view. For status=inactive, sort=apr, fee_apr and roi are served as sort=pnl, because a closed position has no current rate to rank by. Calls on the Active view return the same order as before.

  2. 2026-10-04
    Breaking: internal fields removed from position detail

    GET /v1/positions/{chainId}/{protocol}/{positionId} no longer returns rawSubgraph, _id, __v, the tx* sync fields, rpcFees*, staleMisses, lastVerifiedAt, lastModifyBlock or the _id of nested objects. They were internal bookkeeping that had leaked into the response; every documented field is unchanged.

  3. 2026-10-04
    Uncollected fees are read on-chain and say where they came from

    Open positions' uncollected fees are re-read from the chain by a recurring pass over every open position. A position is also queued for an earlier re-read when the live indexer sees it change, when it is newly indexed, and when its detail is opened. Each position now carries metrics.feesSource — onchain, indexer, settled (a closed position, nothing owed) or unmeasured (not read yet, shown as Pending on /explore) — and metrics.feesMeasuredAt, the time of that reading.

  4. 2026-10-04
    List totals no longer hold up or fail a page

    hasMore on GET /v1/positions and GET /v1/pools is always exact. pagination.total is exact when the count finishes in time; otherwise it is the smallest total the page itself proves, and pagination.totalCapped is true. Totals stop at 25,000, also flagged totalCapped. On GET /v1/pools and pool detail, positionsCount and holdersCount (open positions and their distinct owners) are null when they could not be counted in time, with countsPending: true on the list, where the whole request used to fail with 503.

  5. 2026-10-04
    All-chains lists answer when one chain is slow

    When chainId is omitted and one chain does not answer in time, GET /v1/positions and GET /v1/pools return the other chains' rows with partial: true and missingChains naming the chains left out, instead of failing the whole request. A response flagged partial is refunded.

  6. 2026-10-04
    Responses served from an older read are flagged

    Lists are cached for 15 seconds. When a list, count, pool detail or position detail response has to be served from an older read — a timed-out read answered from its last good copy, a database replica running behind, or a very deep page computed from an older offset — it carries stale: true and dataAsOf, the time the data was read. The flag is about when the stored data was read, not how recently each figure in it was updated: uncollected fees carry their own metrics.feesMeasuredAt.

  7. 2026-10-04
    Deep pages have no depth limit

    Any page of GET /v1/positions and GET /v1/pools can be requested, however deep. A deep page that cannot be computed within one request answers 503 with a Retry-After header instead of being refused; progress towards the page is kept where possible, so retry after the interval the header gives. Every 5xx is refunded.

  8. 2026-10-04
    Position detail and charts no longer wait for a long transaction history

    For a position with a long transaction history, the detail route no longer waits for the full history to load. When the full list is not ready it returns the newest page of transactions, flagged transactionsPartial: true and transactionsDegraded: true, while the rest loads in the background; a later request returns the complete list. Position charts in the same situation return without the position series, with partial: true and missing: ["transactions"]. Chart ranges are measured back from the newest data point rather than from today. Responses flagged this way are refunded.

  9. 2026-10-04
    /v1/stats counts pools from the last complete pool sync

    GET /v1/stats now takes pools and tvlUsd, overall and per chain, from the totals recorded at the end of each chain's last complete pool sync, so those figures can be up to one sync old. When that sync could not price some pools, the figures fall back to the stored pool rows, so those pools still count towards pools and tvlUsd.

  10. 2026-09-07
    Robinhood Chain

    Robinhood Chain (chain ID 4663) is served for Uniswap v4 on GET /v1/positions and GET /v1/pools, and appears in GET /v1/chains as robinhood. Its native token is ETH and USDG is the quote stablecoin. Two things are worth knowing before you rely on symbols there. Token symbols on this chain are heavily squatted, and the usual heuristics pick the impostor rather than the real token — an 18-decimal "USDG" is held by more addresses than the real 6-decimal one, and a counterfeit wstETH matches the genuine token on symbol, name and decimals alike — so we resolve the majors from a fixed address list and flag every other claimant to those symbols. Displayed liquidity is also forgeable on a chain this new: a pool can report billions in reserves while holding cents on the side that matters, so a token is priced from a market quote only where there are real two-sided reserves behind it, and is reported at 0 rather than at a fabricated figure otherwise.

  11. 2026-09-07
    Filter positions and pools to tokenized equities

    GET /v1/positions and GET /v1/pools accept assetClass=equity, which returns only the rows where at least one leg is a tokenized equity — currently 193 of them on Robinhood Chain, covering tickers such as NVDA, SPY and SPCX. The only accepted value is equity; omitting the parameter returns everything as before, so nothing changes for existing calls. Equity legs also report their ticker as the symbol where the on-chain symbol was unreadable.

  12. 2026-09-07
    Position detail no longer fails when transaction history is unavailable

    GET /v1/positions/{chainId}/{protocol}/{positionId} used to return 500 when the upstream indexer could not supply the position's transaction list, discarding a response whose every other figure had already been computed. It now returns 200 with transactions as an empty array and a new boolean transactionsDegraded set to true. Treat transactionsDegraded as "unknown", never as an empty history: it is the only thing distinguishing a position with no transactions from one whose transactions we could not load, and it is always present, so it can be read without an existence check. A response flagged this way is refunded and shown as refunded in your usage breakdown rather than charged as a successful call. If you relied on the 500 to detect this condition, switch to the flag.

  13. 2026-08-31
    The chain and protocol pickers open on a phone

    Tapping "All chains" or "Uniswap v4" on /explore did nothing on a touch screen. A tap fires a phone browser's emulated hover before the tap itself, which opened the menu and then immediately closed it again, so neither picker could be opened on a phone at all — the chain and protocol filters were unreachable there. Both now open on tap, close when you tap elsewhere, and their panel opens full-width under the row instead of stretching the pill you tapped and pushing the other one onto a second line. Mouse and keyboard behaviour is unchanged.

  14. 2026-08-31
    /explore fits a phone screen

    The heading, filters and sort chips on /explore were sized for a desktop and took most of a phone screen before any data appeared. They are now scaled for the viewport: the first position is visible without scrolling on a 375px screen, the refresh button is a full-size tap target rather than an 18px sliver, and the "filters" caption — which only labelled filters already on screen — is dropped on phones. The position and pool cards on phones and tablets are tighter too, about a quarter shorter, because each field was being drawn at a desktop table row's height and inset; the "*" that marks an APR skewed by a short position age now sits beside the figure instead of on a line of its own. Pool rows on a phone carry their labels (TVL, 24h volume, 24h vol/TVL, 24h fees, fee APR, age); they had been showing six unlabelled numbers. In a position's detail, the chart tabs (PnL, Assets value, APR, Fees APR, Div. loss) sit on one line at a single height — the longer labels had been wrapping to two lines, leaving the row a mix of tall and short buttons. The desktop table is unchanged.

  15. 2026-08-31
    Search on /explore says what it searched, and wallet search covers every page

    Pasting a wallet address into the search box on /explore now filters every position that address holds, not just the ones on the page you were looking at — it goes to the API as the owner filter, the same one GET /v1/positions accepts. Searching a token or pool name still filters only the rows already loaded, because the positions endpoint has no name search, and the page now says so instead of leaving you to infer it: the line beside the pager reads either "3 of 25 on this page match" or the total number of positions matching, the pager is hidden when there is only one page, and it is labelled "page 2 of 305" rather than "2 / 305". Any search starts again at page 1, and a search that matches nothing explains which of the two happened and offers to clear itself.

  16. 2026-08-31
    A position on /explore has a link of its own, and can be refreshed in place

    Opening a position on /explore now puts it in the address bar as ?chain=&protocol=&position=, and that URL reopens the same position's detail panel on load. The share button in the panel hands out that link — through the operating system's share sheet where there is one, and to the clipboard everywhere else. The panel's overflow menu adds Refresh data, which re-reads that one position's metrics and charts without reloading the page, alongside copy actions for the link, the NFT ID and the owner address. The reference-basis picker in the same header (HOLD / USD) was a control that only responded to a click on its own text; the whole button opens it now, and "expand charts" below it works: it widens the charts to the full panel, with the metrics moving beneath them, and switches back to "collapse charts".

  17. 2026-08-26
    Usage broken down by day, endpoint, key and response class

    GET /account/usage/detail returns your consumption over a date window split four ways: units and requests per day, per endpoint alongside that endpoint's current unit cost, per API key, and per response class. from and to are YYYY-MM-DD and default to the 1st of to's month through today, with 92 days the widest single window; an optional groupBy of day, routeKey, apiKey or status returns only that breakdown. 2xx, 4xx, 5xx and 429 are counted separately, so a rate-limited spike is distinguishable from a bad-request spike, and calls we refunded are shown as refunded rather than folded into a failure rate. A key that has since been deleted keeps its prefix and label in the history it generated. /portal/usage renders the same figures, and GET /account/usage now includes requests alongside units on each month of history. The per-request detail begins with this release: months before it were recorded only as a monthly total and stay that way.

  18. 2026-08-26
    Rate-limit and quota headers are readable from the browser

    X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset are now exposed to browser JavaScript on /v1 responses. They were always sent, but a browser key could not read them because they were absent from the CORS exposed-headers list, so a browser client could not see its own remaining quota that the documentation said was there. A 429 now also carries X-RateLimit-Reason, either rate or quota, naming which of the two limits was hit.

  19. 2026-08-19
    Breaking: liquidity distribution is active liquidity, and tick prices are readable

    The liquidityDistribution.ticks[] array on GET /v1/positions/{chainId}/{protocol}/{positionId}/charts and GET /v1/pools/{chainId}/{protocol}/{poolId}/charts gained liquidityNet and activeLiquidity, so the array describes depth at each tick instead of only where positions begin and end. Breaking, in the same array: price0 and price1 are now decimal-adjusted, where they were the raw 1.0001^tick in base units — a 6/18-decimal pair that reported 9.99e+11 now reports 0.9999 — and amount0/amount1 are derived from active rather than gross liquidity. Anything that re-scaled price0/price1 by token decimals itself must stop doing so.

  20. 2026-08-19
    A verification code now says whether it was actually sent

    POST /account/auth/resend-verification returns a delivered flag alongside ok. Previously the response was the same whether the code reached your inbox or the mail provider refused it, and the portal told you to go and check an inbox that was never going to receive anything. An explicit delivered: false means the code is valid and stored but was not sent — a fault on our side, not something a retry fixes. The field is absent on the paths where nothing needed sending.

  21. 2026-08-19
    The pricing page reads plan prices live

    Plan prices, quotas and rate limits on /pricing were served from a one-hour cache, so a price change could leave the page advertising the old figure for up to an hour while the portal quoted the new one. The plan catalogue is now read per request. The unit allowances shown on the home page come from the same live catalogue.

  22. 2026-08-19
    Arbitrum One positions and pools indexed

    Uniswap v4 positions and pools on Arbitrum One (chainId 42161) join Ethereum (1) and Avalanche C-Chain (43114), on every positions and pools route.

  23. 2026-08-19
    GET /v1/chains lists only chains with a live source

    The free chains endpoint previously returned every chain in our registry, including ones with an empty protocols array that could not answer a query. It now lists only chains that actually have data behind them, so the response is safe to drive a chain picker from.

  24. 2026-08-15
    A settled payment is never lost or counted twice

    A USDC transfer that lands against an invoice already voided by a plan change, or after a checkout was abandoned, is credited to your account balance and spent on the next invoice instead of leaving you on Free and asked to pay again. Money diverted to balance is no longer also summed as having paid its invoice.

  25. 2026-08-15
    Cancel a pending plan change, and see confirmations

    A started-but-unpaid plan change can be called off with POST /account/billing/cancel-change, returning the proration credit it consumed — previously the only control ended the plan you were paying for. While a payment confirms, the portal shows confirmations against the required minimum with the transaction hash and an explorer link.

  26. 2026-08-14
    CopyPools is now Tickwise

    The product has a new name and new branding, and documentation examples now show the Tickwise host. No client change is required: the KC-APIKey header and the kc_live_ key prefix are unchanged, your keys keep working, and existing portal sessions survive the rename.

  27. 2026-08-14
    API playground in the portal

    Pick an endpoint at /portal/playground, fill in the parameters, send the call with your own key and see the status, timing, response body and units spent. The key is never stored, logged or echoed back.

  28. 2026-08-14
    GET /v1/stats gained per-chain totals

    The free stats endpoint added activePositions (open positions only), tvlUsd and a byChain array carrying chainId, protocol, pools, positions, activePositions, tvlUsd and indexedUpToTimestamp. Purely additive. tvlUsd counts only TVL that passed our valuation checks, so treat it as a lower bound.

  29. 2026-08-14
    Public documentation, pricing and status pages

    Quickstart, authentication, per-endpoint references for positions, pools and metadata, a units-and-limits page, an error reference and a coverage matrix generated from the same registry the API answers from. Plus public pricing and a status page showing how far each chain is indexed.

  30. 2026-08-13
    Self-serve plans, paid in USDC

    Choose a plan in the portal and pay in USDC by signing an EIP-3009 transfer authorization; your entitlement follows the invoice. Renewals are raised ahead of the period end with a grace window and a downgrade to Free — nothing is ever charged automatically.

  31. 2026-08-06
    Self-serve signup and customer portal

    Sign up, get a Free plan and issue your own keys with no operator involvement. The portal covers overview, keys, usage and settings, with key creation by type and allowed origins, revoke and delete. Sign in with a password, Google, GitHub or a wallet; email addresses are proved by a six-digit code, and password reset runs on a single-use 30-minute token.

  32. 2026-08-06
    Unit costs re-tiered by what a call really costs

    Metered routes no longer cost a flat 1 unit each: metadata routes cost 0, detail routes 1, list routes 2 and chart routes 10. The meter and the published price now resolve through a single lookup, so /pricing and your bill cannot disagree. Read GET /v1/pricing/units for the live table.

  33. 2026-08-06
    Refused requests no longer consume quota

    Units are credited back on every 4xx as well as every 5xx, so a request the API rejected — a malformed protocol value returning 400, for example — no longer decrements X-Quota-Remaining. 429s were never charged.

  34. 2026-08-06
    Public /v1/plans, /v1/pricing/units and /v1/stats

    Three unauthenticated, unmetered endpoints: plans with monthlyUnits, rateLimitPerMin, maxApiKeys, allowBrowserKeys and prices in integer minor units; the per-route unit cost table; and index counts with pools, positions and indexedTo.

  35. 2026-07-18
    owner filter on the positions endpoints

    GET /v1/positions accepts owner=0x… (case-insensitive) to fetch every indexed position held by one wallet. It composes with the other filters, so pass status=all&minValueUsd=0&minPoolTvl=0&includeRisky=true for a wallet's complete set.

  36. 2026-07-03
    Avalanche C-Chain positions and pools indexed

    Uniswap v4 on Avalanche C-Chain (chainId 43114, native AVAX) is served on every positions and pools route alongside Ethereum.

  37. 2026-07-03
    Breaking: omitting chainId now returns every chain

    chainId became optional on /v1/positions and /v1/pools. A request that omits it previously defaulted to Ethereum; it now returns a merged, re-sorted result across every enabled chain with summed pagination totals. If you relied on the old implicit default, pass chainId=1 explicitly.

  38. 2026-06-24
    GET /health reports indexed block vs chain head

    The free health route returns indexedBlock, chainHeadBlock, lagBlocks, synced and status per integration, so you can show data freshness rather than guessing at it. The older indexedUpToTimestamp field is retained.

  39. 2026-06-17
    Breaking: keyless /api/* routes closed to third parties

    In production the open /api/top-* dashboard routes now require a request Origin in our allowlist; any other or missing Origin is refused with 403. Server-side and scripted callers must use /v1 with a KC-APIKey header, which is a documented, versioned surface with quota headers.

  40. 2026-06-16
    Server and browser API keys, with origin binding

    Every key now has a type. A server key is refused with 403 on any request carrying an Origin header, so a key pasted into frontend JavaScript stops working. A browser key must be issued with at least one allowed origin and works only from those origins, making a leaked key useless on another site.

  41. 2026-06-16
    Metered pool and chart endpoints

    GET /v1/pools, GET /v1/pools/{chainId}/{protocol}/{poolId} and the charts routes for both pools and positions, each metered under its own route key. range accepts 30d, 60d, 90d, 180d or all and defaults to 30d. Pool responses also carry positionsCount and holdersCount, both counting open positions only.

  42. 2026-06-12
    Valuations gated on real market activity

    A token leg that is neither an anchor nor allowlisted is only valued when an independent DEX source corroborates it with real 24h volume and counter-side liquidity. Fabricated and dead-pool positions that previously reported large figures now return $0 or drop out of the default lists. Responses gained priceConfidence, nominalValueUsd, unrealizableLeg and priceUnavailable so you can see why.

  43. 2026-06-01
    Metered /v1 API with keys, plans and quota headers

    The versioned API launched: /v1/positions list and detail behind a KC-APIKey header, with /v1/chains and /v1/protocols free. Four plans from Free to Enterprise with monthly unit quotas and per-minute rate limits. Every metered response carries X-RateLimit-Limit, -Remaining, -Reset and X-Quota-Limit, -Remaining, -Reset; exceeding either returns 429 with Retry-After.

  44. 2026-05-13
    Uniswap v4 positions on Ethereum

    The first release: ranked Uniswap v4 liquidity positions on Ethereum, sortable by APR, fee APR, ROI, age, PnL or underlying value, with filters for status, range, minimum value and pool TVL, and a detail view carrying per-position amounts, fees, PnL and transaction history.