API conventions
Every TraceFast API method follows the same rules, whether it is called with an API key or paid for with x402. This page states them once; the method pages only add what is particular to a method.
Requests
| Rule | Value |
|---|---|
| Base URL | https://tracefast.xyz/api/v1 with an API key; https://mpp.tracefast.xyz/api/v1 over x402 |
| Chain | a numeric path segment: /chains/1/…. 1 — Ethereum mainnet — is served; any other value answers 404 chain_not_supported |
| Families | resolve to turn a name into an address; entities/{address}/… for everything keyed by an address; tokens/{token}/…; blocks/…; transactions/{hash}/…; traces/{hash}/…; contracts/{address}/…; consensus/…; risk/blacklists/{address}/… |
| Addresses and hashes | input: 0x-prefixed hex, any case. Output: always lowercase. An address is 40 hex characters, a hash 64 |
| Point in time | ?block=N — the state at the end of block N, on every method that has a past form. Absent: the current state |
| Ranges | ?fromBlock=&toBlock= on the feeds |
| Parameter names | camelCase, one spelling each, no aliases |
| Unknown or repeated parameter | 400 bad_request, naming the parameter. A parameter is never accepted and ignored |
| Method | GET only; anything else answers 405 method_not_allowed |
| Request line | at most 2048 bytes; longer answers 414 uri_too_long |
Responses
A successful call answers 200 application/json with two members:
{
"data": { "…": "…" },
"meta": {
"chain": "ethereum",
"timestamp": 1789047095512,
"cached": false,
"asOfBlock": 25947302,
"docs": "https://docs.tracefast.xyz/api/block-latest"
}
}- Numbers that can exceed 2^53 — amounts, balances, gas, wei — are JSON strings in decimal. Block heights and counts are JSON integers.
- Field names are camelCase.
- Absence is not falsity. A fact that is missing from an answer is not
proven — it never means the opposite. Where a method has a place for an
absent value, the page says whether it is omitted or
null, and what that means. - Methods answer with facts. The one exception is
resolve, on the Intelligence plan: it returns the name
TraceFast shows for an address and how TraceFast presents it (
name,nameStanding,impersonates). No other method carries names or labels, and none carries where a name comes from, a taxonomy or tags.
meta
| Field | Meaning |
|---|---|
chain | ethereum for chain id 1 |
timestamp | when this response was produced, Unix milliseconds |
cached | true when the answer came from the response cache. Cached answers are refreshed within minutes |
asOfBlock | the block the answer is valid at. Absent when the method states no block — absence means "not stated", never "latest" |
coverage | named block heights that bound what the method can state — for example the first block a derived fact is computed from, or the last block a lagging fact has reached. Present only on methods whose coverage is bounded; each such method page lists its keys. Read it on every call: the bounds move |
docs | the documentation page of the method that answered |
meta is stamped per response, so a cached body never carries a stale meta.
Errors
Every refusal is JSON — never plain text:
{
"error": {
"code": "block_not_available",
"message": "block is past the covered history; the highest block that can be answered is 25901228",
"details": { "reason": "block_not_available", "maxAvailableBlock": 25901228 },
"requestId": "01a07bc5-0da1-7f3e-9c2a-…"
}
}code— a stable word from the table below. Branch on it.message— for a human. It may be reworded; never parse it.details— present only when there is something to act on: areasonthat refines the code, and the bound that was hit. Absent — nevernull— when there is nothing to add.requestId— quote it when you contact support. It is also in theX-Request-Idresponse header.
The key check (401, 403) and the rate limits (429) are described on
API keys; over x402, a 402 is the
payment challenge — see Paying with x402.
code | HTTP | Meaning |
|---|---|---|
bad_request | 400 | the request is outside the contract: a malformed address or hash, a parameter out of range, an unknown or repeated parameter. details.reason says which |
invalid_cursor | 400 | the cursor is not valid for this request. Restart from the first page |
not_found | 404 | no method at this path, or nothing at the key the path names — a block above the chain head, an unknown transaction |
chain_not_supported | 404 | the chain id is not served |
method_not_allowed | 405 | the path exists, the HTTP method does not |
uri_too_long | 414 | the request line is over 2048 bytes. details.maxUriBytes |
block_not_available | 422 | the request is well-formed, but block is outside the history covered right now. details.maxAvailableBlock, and minAvailableBlock where there is a floor — see data freshness |
portfolio_too_large | 422 | the address holds more distinct positions than the method can value. details.maxPositions |
token_not_covered | 422 | no balance — and so no holder list or count — can be stated for this token. details.token |
balance_not_available | 422 | no balance of this address and token is recorded at or before block. details.token, details.block |
service_unavailable | 503 | a transient limit on our side, or the coverage bound of the data could not be read. Retry with backoff |
internal_error | 500 | our fault. No details, by design |
A 422 is not a failed request: it states what the API can and cannot answer.
It is never replaced by a zero, an empty list or a truncated answer.
details.reason
reason | Under code | Carries, or means |
|---|---|---|
address_invalid | bad_request | the address is not 0x + 40 hex characters |
tx_hash_invalid | bad_request | the transaction hash is not 0x + 64 hex characters |
topic0_invalid | bad_request | the event signature hash is not 0x + 64 hex characters |
attribute_id_invalid | bad_request | the attribute or relation id is not a short identifier of letters, digits and :_.- |
block_not_positive | bad_request | block |
block_number_invalid | bad_request | number — the block in the path is not a non-negative integer |
block_required | bad_request | the method answers about one block and block was not passed |
block_not_supported | bad_request | block — the method has no past-block form |
block_window_inverted | bad_request | fromBlock, toBlock — the lower bound is above the upper |
limit_out_of_range | bad_request | limit, maxLimit |
query_missing, query_too_short, query_too_long, query_invalid | bad_request | q is missing, shorter than 2 or longer than 100 characters, or carries control characters |
query_is_hash | bad_request | q is a 32-byte hash — a transaction or a block, not a name or an address |
unknown_parameter | bad_request | the message names the parameter and the accepted ones |
duplicate_parameter | bad_request | parameter |
direction_invalid | bad_request | direction is outside the method's vocabulary |
status_invalid | bad_request | status is outside 1 / 0 |
period_invalid | bad_request | period is outside the method's windows — each method page lists its own |
event_type_invalid | bad_request | eventType is outside the token-transfer event vocabulary |
include_calldata_invalid | bad_request | includeCalldata is outside true / false |
validator_index_invalid | bad_request | index in the path or validatorIndex in the query is not a non-negative integer |
group_by_invalid | bad_request | groupBy is outside validator / address |
block_window_too_wide | bad_request | span, maxSpan, fromBlock, toBlock — the block window is wider than the method admits; narrow it |
issuer_contract_invalid | bad_request | issuerContract is not 0x + 40 hex characters |
action_invalid | bad_request | action is outside add / remove / enforce |
peer_count_over_limit | bad_request | peerCount, maxPeerCount — the past-block ranking cannot be computed for an address with this many counterparties |
row_count_over_limit | bad_request | rowCount, maxRowCount |
window_row_count_over_limit | bad_request | the feed window is too wide for this address; narrow fromBlock / toBlock |
filter_needs_block_window | bad_request | filters, rowCount, maxUnboundedRowCount, spanBlocks, maxSpanBlocks — too many rows to filter without a block window; pass fromBlock / toBlock |
invalid_cursor | invalid_cursor | — |
uri_too_long | uri_too_long | maxUriBytes |
block_not_available | block_not_available | maxAvailableBlock, minAvailableBlock when there is a floor |
coverage_gap | block_not_available | coverageGapFromBlock, maxAvailableBlock, and coverageGapToBlock unless the gap is open-ended — see below |
portfolio_too_large | portfolio_too_large | maxPositions |
token_not_covered | token_not_covered | token |
no_history_row | balance_not_available | token, block |
Numbers a client acts on are in details, built from the same values as the
sentence in message.
Data freshness
Most TraceFast methods answer up to the head of the chain. Some data is built behind the head, and the methods that serve it refuse rather than answer empty: an empty list would mean "nothing happened in this block", and that is a statement we do not have.
The refusal is HTTP 422 with error.code block_not_available:
{
"error": {
"code": "block_not_available",
"message": "block is past the covered history; the highest block that can be answered is 26091091",
"details": { "reason": "block_not_available", "maxAvailableBlock": 26091091 },
"requestId": "01a0f2e7-0e5e-7eb0-ab47-53e3b52bffde"
}
}details.maxAvailableBlock— the highest block the method can answer right now. Ask again at that block, or repeat the request later.maxAvailableBlockis in every422but one. On the methods keyed by a transaction hash — trace, call frames, why it reverted, storage writes — the data may not have reached the transaction at a moment when its own bound cannot be read either. The refusal is thenreason: coverage_gapwith no bound, and the only move is to retry later. Code that takesmaxAvailableBlockand asks again must check that it is present. On methods keyed by a block number the same situation answers503instead.details.reasoncoverage_gap— a different case: the block exists, but it sits in a range that is temporarily unavailable. The bounds of the range are incoverageGapFromBlockandcoverageGapToBlock. It never means "no such block" — retry later; see coverage gaps.- HTTP
503service_unavailable— the coverage bound could not be read. Retry with backoff. - HTTP
404not_found— the block is above the head of the chain: it does not exist yet.
What trails the head, as it stood on 2026-09-30. This is an example on a date, not a guarantee — the values move, so go by the response, not by this list:
| Method | Reach on 2026-09-30 |
|---|---|
| One block, block transactions, block withdrawals, block addresses | the head, no lag |
| Validator withdrawals | behind the head by minutes; the same withdrawals reach block withdrawals first |
| Block authorizations, why it reverted | built behind the head: usually minutes to tens of minutes, at times an hour or more. The 422 names the highest block you can ask about |
An empty list on HTTP 200 always means "we looked, and there is nothing" —
never "we have not looked yet".
Coverage gaps
A range of blocks can be temporarily unreadable here even though it exists on
chain — a coverage gap. Any method may then answer 422 block_not_available with reason: coverage_gap and the bounds of the gap in
details. The verdict comes before the read: a method keyed by a block never
answers an empty page for a block inside the gap.
The same refusal covers keys whose block is not known in advance:
- A transaction hash — absence cannot be confirmed while a gap is open.
- An address or a token contract with no recorded history —
its first activity, or its first transfer, could sit inside the gap, so the
API answers
coverage_gaprather than404 not_found. The message names the key it is about;detailsis the same. - Execution traces and the classification of failures become available
minutes to hours after the transaction, sometimes later. A transaction whose
trace or failure classification is not available yet is refused with the
maxAvailableBlockthat data has reached;coverageGapFromBlockis the next block, andcoverageGapToBlockis absent because the range is open-ended. The refusal says nothing about the transaction's outcome.
Read it as "ask again later", never as "this does not exist". Requests at or
below maxAvailableBlock outside the gap are unaffected, and the refusal
disappears on its own when the gap closes. Retrying within seconds is
pointless; within hours it is not. Where a method's reach is bounded, its
meta.coverage names the bounds on every answer.
Pagination
Feeds page by keyset, never by offset — ?page= and ?offset= are refused
by name.
limit— rows per page,1up to the method's cap; the cap and the default are on each method page.nextCursor— indatawhile there are more rows; absent on the last page.cursor— passnextCursorback unchanged, with the same filters. A cursor is sealed to its method, chain, direction and filters: altered, or reused under different ones, it answers400 invalid_cursor. A cursor can also expire. The recovery is the same in every case — restart from the first page.- A page holds at most
limitrows and may hold fewer whilenextCursoris still present: a page of large rows — an event log'sdatais bounded only by gas — is cut by size, never by cutting a value. A single row larger than that is served whole. totalis stated only where it is cheap to know.
Stability
Every public method is stable since 2026-10-01. A method added later may start in beta; its page says so.
| Stability | Promise | Retirement |
|---|---|---|
| Beta | the shape may still change, with notice | no notice window |
| Stable | frozen; a breaking change becomes a new method | at least 90 days between the announcement and the sunset |
A retired method is marked deprecated in the OpenAPI document and announces
itself on every response with Deprecation, Sunset and Link headers — the
last pointing to its documentation and its successor. Nothing is deprecated
today.
Headers
| Header | On | Value |
|---|---|---|
Content-Type | every response | application/json |
Cache-Control | success | private, max-age= the seconds the cached answer has left |
Cache-Control | refusal | no-store |
X-Request-Id | every response | the request's id |
X-Content-Type-Options | every response | nosniff |
The OpenAPI document at https://tracefast.xyz/api/v1/openapi.json needs no
key and answers conditional requests: its ETag is its info.version, so an
unchanged document costs a 304.
Related pages
- API keys — sending the key, plans, refusals and limits
- Connect an AI agent over MCP — the same rules, as tools
- Paying with x402 — the key-less way, for AI agents
- API overview — every method, by family
Last updated 22 Oct 2026, 08:06 UTC · verified against TraceFast v1.0.261 · Markdown