Support

Help centre

The failures worth recognising on sight. Each row is the literal response the API returns, so you can match what you are looking at against it — but branch your code on the status code, never on the message.

  • 401 · Missing KC-APIKey header
    The header is absent. Usually a proxy or client library that drops unknown headers, or an Authorization: Bearer that was never read.
    Send the key as KC-APIKey. Not a bearer token, and never a query parameter.
  • 401 · Invalid or revoked API key
    The key is not recognised, has been revoked, or belongs to a suspended account. A whitespace-padded copy-paste looks identical and fails the same way.
    Issue a fresh key in the portal. The secret is shown once and stored hashed, so a lost key is replaced rather than recovered.
  • 403 · This API key is server-only and cannot be used from a browser
    A server key sent a request carrying an Origin header — which is to say, from a browser. The key is valid; the place it was used is not.
    Call the API from your backend, or issue a browser key with your origins listed. This 403 is the feature: it stops a secret key going public silently.
  • 403 · Origin not allowed for this API key
    A browser key was used from an origin it is not bound to. Origins are matched exactly on scheme://host[:port], so https://app.example.com and https://www.app.example.com are different entries, as are ports in local development.
    Add the exact origin to the key. Paths and wildcards are rejected at issue time, not silently ignored.
  • 429 · Rate limit exceeded
    The burst limit, counted in requests per key over a rolling window. Retry-After will be seconds.
    Honour Retry-After. This one clears on its own; a tight retry loop is what turns it into a sustained one.
  • 429 · Monthly quota exceeded
    The monthly unit allowance, counted across every key on the account. Retry-After will be large — it points at 00:00 UTC on the 1st.
    Upgrade, or wait for the reset. A quota 429 consumes nothing, so there is no charge for hitting it.
  • 400 · Unsupported chainId · Unsupported protocol · Invalid poolId
    A coverage or format boundary. We serve Uniswap v4 on Ethereum (1) and Avalanche (43114); a v4 poolId is 0x plus 64 hex, a v3 one 0x plus 40.
    Check coverage before treating it as a bug. Any 4xx is refunded, so a malformed request costs no units.
  • 404 · Position not found · Pool not found
    No such id on that chain and protocol. Distinct from an empty list: a filtered search that matches nothing returns 200 with an empty array.
    Confirm the chainId and protocol in the path. An id from one chain will 404 against another.

Two answers that look wrong

An empty list is a valid 200. The list endpoints apply visibility filters by default — a minimum position value, a minimum pool TVL, and an exclusion of positions whose token has no corroborated market. A wallet holding only small or spam-token positions legitimately returns nothing. Widen them with minValueUsd=0&minPoolTvl=0&includeRisky=true&status=all before concluding the data is missing.
A missing price is reported, not faked. When a token cannot be priced from any independent source the position carries metrics.priceUnavailable: true rather than a misleading zero. Treat that as unknown, not as worthless — a genuine zero also exists, and the two mean different things.

Before you escalate

Check indexing freshness first. It reports the last indexed block against the chain head per chain, which is what separates “this data does not exist” from “this data has not been indexed yet” — and the second one resolves itself.

Still stuck?

We do not stamp a request id on responses, so a trace starts from what you can see. Have ready: the full request URL including query string, the UTC timestamp, the status code, the verbatim error string from the body, and the X-RateLimit-* / X-Quota-* headers if it was a 429. Never send the key itself.

How to reach us →

Longer form on all of this: errors, authentication and units and limits.