Responses and errors
Only GET and HEAD work. Anything else is 405 method_not_allowed. Any website origin may call the API from a browser (CORS).
The envelope
A successful answer is {data, meta, links}.
{
"data": { "kind": "block", "chain": "eip155:1", "number": 26149668 },
"meta": {
"finality": "finalized",
"as_of": { "eip155:1": { "block": 26149668, "finalized_block": 26155479 } },
"partial": false,
"missing": []
},
"links": { "self": "/v1/chains/eip155:1/blocks/26149668" }
}The data above is shortened. A real block has more fields.
| Field | Meaning |
|---|---|
data | What you asked for. Its kind field names its shape. |
meta.finality | How settled the data is. |
meta.as_of | The block each chain's answer is based on, keyed by chain ID. Empty when the answer is not about a block. |
meta.partial | true when a chain did not answer in time. |
meta.missing | Which chains did not answer, why, and a retry path. |
links.self | The canonical path of what you fetched. |
GET /v1/openapi.json is the one answer that is not wrapped in the envelope.
Finality
| Value | Meaning |
|---|---|
finalized | In a block the chain has made permanent, so it never changes. Ethereum takes roughly 15 to 20 minutes to finalize a block, Monad a few seconds and Arc at once. |
confirmed | In a block that is not final yet, so in rare cases the chain could replace it. An address is always confirmed, and so is every Perpl answer about a snapshot. |
pending | A transaction that is not in any block yet. |
none | Not about a block: the chain list, the status, and most searches. |
mixed | The parts of a combined answer differ. No endpoint returns it today. |
x402 answers are finalized, because the data trails the chain by 10 blocks.
Partial answers
A combined answer, such as an address on every chain, does not fail when one chain is slow. The slow chain is null in data, meta.partial is true and meta.missing explains it.
{
"meta": {
"finality": "confirmed",
"partial": true,
"missing": [
{
"source": "eip155:143",
"reason": "deadline_exceeded",
"retry": "/v1/addresses/eip155:143:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
}
]
}
}Request the retry path to ask again. A partial answer is sent with Cache-Control: no-store.
Amounts and values
- A coin amount is an object:
rawis the exact amount in wei,decimalsandsymboldescribe the coin, andvalueis the amount in whole coins. - Wei amounts are decimal strings, because they can be larger than a JavaScript number holds exactly.
- Counts, block numbers and gas are integers.
- Timestamps are RFC 3339 in UTC to the second, such as
2026-10-08T19:07:59Z. - A field that can be empty is
null. A few optional extras, such as the members ofext.evm, are left out instead.
Coverage notes
Perpl, token, market and Polymarket history answers end with coverage, which names the limits that apply to that answer. Perpl, token and market coverage is a list of codes such as perpl_snapshot_only or token_holders_from_activity. Polymarket coverage is an object with the end of the history, the attribution and the notice that Sona modified the rows. Read it before you show the numbers.
Errors
Every error is application/problem+json (RFC 9457).
{
"type": "urn:sona:problem:invalid_id",
"title": "Invalid identifier",
"status": 400,
"detail": "The block identifier is not valid",
"instance": "/v1/chains/eip155:1/blocks/abc",
"code": "invalid_id"
}| Field | Meaning |
|---|---|
type | urn:sona:problem: followed by the code. |
title | A short, fixed name for the kind of error. |
status | The HTTP status, repeated. |
detail | A sentence about this request. The wording may change, so do not parse it. |
instance | The path of the request that failed. |
code | The stable machine-readable reason. |
retry_after_s | Seconds to wait, on rate_limited, credits_exhausted and upstream_unavailable. |
Switch on code, not on the text.
Error codes
| Status | code | Meaning |
|---|---|---|
| 400 | invalid_id | An ID in the path is malformed, or a mixed-case address has a wrong checksum. |
| 400 | invalid_query | A query key the route does not take, a key sent twice or empty, or a value out of range. |
| 400 | payments_not_supported | A PAYMENT-SIGNATURE or X-PAYMENT header was sent. |
| 404 | unsupported_chain | The chain ID is well formed but not served. |
| 404 | not_found | Nothing is there. |
| 404 | not_found_on_supported_chains | No chain has the transaction hash, or the search text matched nothing. |
| 404 | out_of_range | The block is older than the history Sona serves. |
| 404 | price_not_found | No price is known for the coin. |
| 405 | method_not_allowed | Not GET or HEAD. |
| 429 | rate_limited | Over the rate limit. Wait Retry-After seconds. |
| 429 | credits_exhausted | Over the daily credits. Wait Retry-After seconds. |
| 500 | internal | An unexpected failure. Retrying once is reasonable. |
| 503 | upstream_unavailable | The data source did not answer. Retry after Retry-After seconds. |
| 504 | deadline_exceeded | The answer took longer than the time limit. Retry. |
Every answer except /healthz and /readyz has an X-Request-Id header, never in the body. Quote it when you report a problem. To wait less than an endpoint's default, send Sona-Deadline-Ms with a value of 50 or more.