Errors
Errors are JSON with an error string. Branch on the status code, never on the message — the prose is written for humans and we reserve the right to reword it.
{"error":"Monthly quota exceeded","retryAfterSec":1738368000}Status codes
| Code | Meaning | Retry? |
|---|---|---|
| 400 | A parameter is malformed or unrecognised — a bad sort, an unsupported protocol, a poolId that is not 0x-prefixed hex. | No. Fix the request. Costs no units. |
| 401 | The KC-APIKey header is missing, unknown, revoked, or belongs to a suspended account. | No. |
| 403 | The key is valid but not allowed here. Almost always one of two things: a server key used from a browser (any request carrying an Origin header), or a browser key used from an origin it is not bound to. | No — see Authentication. |
| 404 | No such position, pool or route. | No. |
| 429 | Burst rate limit or monthly quota. Retry-After tells you which by how long it is: seconds for a burst, up to a month for a quota. | Yes, after Retry-After. |
| 5xx | Our problem. | Yes, with backoff. |
Retrying well
Honour Retry-After when it is present; otherwise back off exponentially from about a second, with jitter, and give up after a handful of attempts. Do not retry a 4xx other than 429 — the answer will not change, and a tight loop against a 400 is the fastest way to look like an attack.
Two responses that are not errors
An empty list is a valid answer. The list endpoints apply visibility filters — minimum value, minimum pool TVL, and an exclusion of positions whose token has no corroborated on-chain market. A wallet with only small or spam-token positions returns { "data": [], "pagination": { "total": 0 } } and a 200. Widen the filters (minValueUsd=0&minPoolTvl=0&includeRisky=true&status=all) if you want everything.
A missing price is reported, not faked. When a token genuinely cannot be priced from any independent source, the position carries metrics.priceUnavailable: true rather than a misleading 0. Treat that as “unknown”, not as “worthless” — the two are different, and a valid 0 also exists.
Coverage
We serve Uniswap v4 on Ethereum (1) and Avalanche (43114). Any other chain or protocol is a 400, and that is a coverage statement rather than a bug — no v3, no Arbitrum, no Optimism today.
Still stuck
Check GET /health for indexing status: it reports the last indexed block against the chain head, so you can tell “the data is missing” apart from “the data has not been indexed yet”.