{"openapi":"3.1.0","info":{"title":"opreturn.art","version":"0.1.0","description":"A public, read-only archive of Counterparty (XCP) chain events on Bitcoin L1.\n\n**What makes it worth reading is not that it has the data** — a public Counterparty node has the data too — **but that it states what it does NOT have**, per event type, in a machine-readable form you can check without trusting it. `[]` means we looked and there were none; `null` means we could not ask. They are never interchangeable.\n\n### Prices\n`GET /api/pricing` is the table of contents: every route, what question it answers, what it costs, what is free, what is not for sale and why. Each operation below also carries an `x-price` extension with the same facts, generated from the same single price catalog — the two cannot disagree.\n\n**Nothing is being charged today.** No payment is required, no 402 is emitted and no account exists. The prices are a published list, not a bill. `/api/pricing` reports the live enforcement state per request; this document deliberately does not, because it is cached and would go stale.\n\n### What this map does not yet tell you\n4 of 44 operations publish a 200 response with an EMPTY schema — i.e. \"some JSON\", with no declared shape. Treat those response bodies as undocumented and read the route description. This is a known gap, counted here rather than glossed over.","x-pricing":{"menu":"/api/pricing","source_of_truth":"the price catalog /api/pricing is generated from","live_enforcement_state":"/api/pricing — not this document, which is cached per process"}},"paths":{"/api/health":{"get":{"summary":"Health","description":"Liveness of the ARCHIVE, not of this process. A 200 from a web server that\nis serving data 400 blocks stale would be a lie by omission.","operationId":"health_api_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthEnvelope"}}}},"503":{"description":"⛔ THREE DIFFERENT BODIES SHARE THIS CODE ON THIS ROUTE. `ok: false` with the FULL health envelope means the ARCHIVE is unhealthy and this service is fine — read `ok_reasons` for which of the conditions fired. `{\"detail\": …}` means the DATABASE could not be read at all, so no envelope exists; the handler reads the database before it builds the body. The `{\"error\", \"detail\", \"source\"}` branch never reached this handler at all: it is the PAYMENT MIDDLEWARE refusing before it could identify the resource, which is why a liveness probe can get one from an otherwise healthy service. ⚠️ Branch on the presence of `ok`, never on the status code. ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/HealthEnvelope"},{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/stats":{"get":{"summary":"Stats","description":"Archive size and range. The event count is exact; it is recomputed once per new\nblock (the cache is keyed on the chain tip). `complete_blocks` is a count, not a\nrange: `complete_contiguous_from`..`complete_contiguous_to` is the unbroken run\nof complete blocks ending at the head, and `complete_outside_contiguous` counts\ncomplete blocks below that run.","operationId":"stats_api_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatsEnvelope"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/events/counts":{"get":{"summary":"Event Counts","description":"What the archive holds, per event type. This is our coverage, deliberately not\nthe chain's counts; the difference is what has not been backfilled. Cached\nbriefly on wall-clock time rather than per block, because the backfill moves\nthese counts independently of the chain tip.","operationId":"event_counts_api_events_counts_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max event types in one page","default":1000,"title":"Limit"},"description":"max event types in one page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"event types to skip — the page cursor","default":0,"title":"Offset"},"description":"event types to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventCountsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/events":{"get":{"summary":"Events","description":"Raw archived events, newest first, exactly as the node emitted them. Page back\nwith `before_index`.\n\nCREDIT and DEBIT events are not a paired ledger. Counterparty escrows: the\nDEBIT fires when funds are locked and the CREDIT when they are released, often\nin different blocks or quantities, and sometimes one leg is absent — dispense\nand pool-match credits have no matching debit, and escrowed fairmints and MPMA\nsends carry more credits than debits by construction. `Σ CREDIT − Σ DEBIT` is\nnot a balance; ask a Counterparty node for balances.\n\nThere is also no opening balance here: the archive's contiguous floor is block\n952,800, so a position an address held before that block is invisible. For an\naddress that touched the AMM every movement since block 952,800 is held;\nits position cannot be reconstructed from this route.\n\nNeither is a hole in the event record: at and above block 952,800 the archive\nholds the node's global `event_index` with no gaps. `/api/coverage` publishes\nthe held-versus-on-chain figure for every type.","operationId":"events_api_events_get","parameters":[{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"event type, e.g. NEW_FAIRMINT","title":"Event"},"description":"event type, e.g. NEW_FAIRMINT"},{"name":"block","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2147483647,"minimum":0},{"type":"null"}],"description":"exact block height","title":"Block"},"description":"exact block height"},{"name":"tx_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":100,"title":"Limit"}},{"name":"before_index","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"description":"cursor: event_index to page back from","title":"Before Index"},"description":"cursor: event_index to page back from"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}},"402":{"description":"⭐ PAYMENT REQUIRED, AND THE OFFER IS THE DISCOVERY. This is not an error: the body and the `PAYMENT-REQUIRED` header both carry a complete, signable x402 v2 offer, so a machine can pay and retry without asking a human anything. ⚠️ IT IS A PROPERTY OF THE REQUEST, NOT OF THE ROUTE. Current state at display size is FREE on every route here — that is what the website itself asks for — and only a HISTORY request is charged. The same URL answers 200 and 402 depending on what you asked it for. ⚠️ AND IT IS REACHABLE ONLY WHILE A RAIL IS USABLE. With none configured a priced request is REFUSED 503 rather than offered — see this operation's 503, `reason: no_rail_configured`. A consumer that has never seen a 402 has not proved it cannot arrive.","headers":{"PAYMENT-REQUIRED":{"description":"The same offer, base64-encoded. ⛔ THIS IS THE NORMATIVE COPY — x402 v2 puts the offer in the header and calls the body a server implementation concern. Both are rendered from one method so they cannot disagree.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentOffer"}}}}},"x-price":{"access":"priced","sellable":true,"currency":"USD","unit":"request","metered":false,"price":{"micro_usd":10000,"display":"$0.01","per":"1 request","means":"the total charge for one call to this route"},"free_rows":200,"ceiling":"bounded: whatever the parameters, one response is at most 8,000,000 bytes.","question":"The raw event tape: give me archived chain events, filtered and paged."}}},"/api/launches":{"get":{"summary":"Launches","description":"XCP-69 launch state, derived entirely from events in this archive. Percentages\nand elapsed values are computed against the live tip, never against the block a\nrow was written at. `limit` and `offset` page within EACH bucket (open, pending,\nclosed, nonconformant, unknown_conformance), not across the response.\n\nTwo minter figures, two populations: `totals.minters` is the SUM of per-launch\ndistinct minter counts (an address that minted two launches is counted twice);\n`totals.minters_distinct` counts each minting address ONCE across every XCP-69\nlaunch (open, pending and closed). `minters_basis` says which is which.","operationId":"launches_api_launches_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max launches per bucket (open/pending/closed/nonconformant/unknown_conformance), not per response","default":1000,"title":"Limit"},"description":"max launches per bucket (open/pending/closed/nonconformant/unknown_conformance), not per response"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"launches to skip WITHIN EACH bucket — the page cursor","default":0,"title":"Offset"},"description":"launches to skip WITHIN EACH bucket — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchesEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/mints":{"get":{"summary":"Mints","description":"EVERY VALID COUNTERPARTY FAIRMINT, newest first, from this archive. This route\nis not restricted to the XCP-69 launch set: `population`, `xcp69_only` and\n`population_note` on the envelope say what it holds, and the XCP-69 launch set\nis published per launch and in total by `/api/launches`.\n\n`NEW_FAIRMINT` is fully backfilled and count-verified against the chain, so this\nis complete history, not a window. Only `status='valid'` mints are counted; the\nrejected attempts moved no XCP and are served by `/api/mints/failed`.\n\n`minters` here counts DISTINCT addresses over all fairmints. It is not the same\npopulation as `totals.minters` on `/api/launches` (a sum of per-launch counts\nover XCP-69 launches); `minters_basis` says which is which. Cached per block:\nmints only arrive in blocks.","operationId":"mints_api_mints_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/resolutions":{"get":{"summary":"Resolutions","description":"Launches that have resolved — graduated or refunded. Fairminter statuses are\nopen, pending, closed or `invalid: <reason>`; only `closed` counts as resolved,\nso an invalid record is never reported as a resolution.","operationId":"resolutions_api_resolutions_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max resolutions in one page","default":1000,"title":"Limit"},"description":"max resolutions in one page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"resolutions to skip — the page cursor","default":0,"title":"Offset"},"description":"resolutions to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolutionsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/blocks/{height}/events":{"get":{"summary":"Block Events","description":"Every archived event for one block, plus what this archive claims about that\nblock.\n\n`complete: false` means the archive holds only a subset of event types for this\nblock, so an event type absent here is not absent from the chain. Every height\ngets an answer, including heights below the archive's reach and above the chain\ntip: `coverage`, `coverage_reason` and `missing_types` (the same contract as\n`/api/coverage/block/{height}`) say which, so an empty `result` is never a claim\nthat the block had no events.\n\n`block` can be null beside a non-empty `result`: null means the archive holds no\nblock row for this height (the historical backfill writes events without one),\nand `block_absent_reason` says so.\n\nThis answers \"events in block N\", not \"the chain's state at block N\".","operationId":"block_events_api_blocks__height__events_get","parameters":[{"name":"height","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"block height; any height, including ones above this archive's reach","title":"Height"},"description":"block height; any height, including ones above this archive's reach"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max events in one page; the response also stops at the byte budget, whichever binds first","default":1000,"title":"Limit"},"description":"max events in one page; the response also stops at the byte budget, whichever binds first"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"events to skip, in event_index order — the page cursor","default":0,"title":"Offset"},"description":"events to skip, in event_index order — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockEventsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}},"402":{"description":"⭐ PAYMENT REQUIRED, AND THE OFFER IS THE DISCOVERY. This is not an error: the body and the `PAYMENT-REQUIRED` header both carry a complete, signable x402 v2 offer, so a machine can pay and retry without asking a human anything. ⚠️ IT IS A PROPERTY OF THE REQUEST, NOT OF THE ROUTE. Current state at display size is FREE on every route here — that is what the website itself asks for — and only a HISTORY request is charged. The same URL answers 200 and 402 depending on what you asked it for. ⚠️ AND IT IS REACHABLE ONLY WHILE A RAIL IS USABLE. With none configured a priced request is REFUSED 503 rather than offered — see this operation's 503, `reason: no_rail_configured`. A consumer that has never seen a 402 has not proved it cannot arrive.","headers":{"PAYMENT-REQUIRED":{"description":"The same offer, base64-encoded. ⛔ THIS IS THE NORMATIVE COPY — x402 v2 puts the offer in the header and calls the body a server implementation concern. Both are rendered from one method so they cannot disagree.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentOffer"}}}}},"x-price":{"access":"priced","sellable":true,"currency":"USD","unit":"request","metered":false,"price":{"micro_usd":10000,"display":"$0.01","per":"1 request","means":"the total charge for one call to this route"},"free_rows":200,"ceiling":"bounded: whatever the parameters, one response is at most 8,000,000 bytes.","question":"Everything archived at one block height."}}},"/api/pools":{"get":{"summary":"Pools","description":"Every AMM pool, with its latest reserves, from this archive. The pool\nlifecycle — OPEN_POOL (creation, with its reserves) and every later\nPOOL_UPDATE — is held complete from the AMM's activation block, 952,800, to the\ntip. That completeness is the archive's own backfill claim, checked against\nwhat the node reported when the backfill ran; `/api/coverage` states\ncompleteness type by type.\n\nThe AMM's other tapes are separate routes: `/api/pool/swaps`,\n`/api/pool/deposits` and `/api/pool/withdrawals` (withdrawals have not been\nhistorically backfilled). `changed_at`: null means the block-to-time map does\nnot cover that block yet; it is never the time we fetched the row. `stale_fallback`\nand `freshness` say how current each half of the body is.","operationId":"pools_api_pools_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max pools in one page","default":1000,"title":"Limit"},"description":"max pools in one page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"pools to skip — the page cursor","default":0,"title":"Offset"},"description":"pools to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoolsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/markets/volume":{"get":{"summary":"Markets Volume","description":"DEX volume per pair over a trailing window of `hours`, from this archive, which\nholds every ORDER_MATCH there has ever been. A pair absent from the response\ntraded nothing in the window — a measured zero — but only while `truncated` is\nfalse; check it before reading absence as zero. Expired matches are excluded.\n`vol_xcp` is null when the quote is neither XCP nor BTC (\"cannot be expressed in\nXCP\"), never zero.","operationId":"markets_volume_api_markets_volume_get","parameters":[{"name":"hours","in":"query","required":false,"schema":{"type":"integer","maximum":168,"minimum":1,"default":24,"title":"Hours"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"description":"max pairs in one page","default":5000,"title":"Limit"},"description":"max pairs in one page"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"pairs to skip — the page cursor","default":0,"title":"Offset"},"description":"pairs to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketVolumeEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/markets/stats":{"get":{"summary":"Markets Stats","description":"Per pair: last fill, previous fill and lifetime fill count, from this archive.\nThe response covers pairs that traded within the last `days` days plus every\npair with an AMM pool, so a pair absent here is not a claim that it never\ntraded. Pool pairs are matched on the unordered pair. Expired matches are\nexcluded, so `last` is a price somebody actually paid.","operationId":"markets_stats_api_markets_stats_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":3650,"minimum":1,"default":90,"title":"Days"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max pairs in one page; the byte budget may stop it sooner","default":1000,"title":"Limit"},"description":"max pairs in one page; the byte budget may stop it sooner"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"pairs to skip — the page cursor","default":0,"title":"Offset"},"description":"pairs to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketStatsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/markets/ranking":{"get":{"summary":"Markets Ranking","description":"The terminal's whole market ranking — every pair, ranked, with the same rows and\nkeys the page shows.\n\n`vol_quote`, `vol_xcp` and `trades` cover a trailing block window, published as\n`vol_window_blocks` and `vol_window_floor_block`; `trades_all` is every\ncompleted fill held for the pair. `vol_xcp` is null when the quote is neither\nXCP nor BTC, never zero. `chg` is measured against the previous fill at\nwhatever age (`prev_t`), not over 24 h. A pair is listed if it has a resting\norder, an AMM pool, or a fill at or after `membership_since_block`; absence is\nnot a claim about the chain.","operationId":"markets_ranking_api_markets_ranking_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MarketRankingEnvelope"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/rates":{"get":{"summary":"Rates","description":"BTC per XCP: a plain volume-weighted average over the most recent XCP\ndispenses. Dispenses are used because the BTC payment is the trigger, so each\none proves BTC moved; the DEX's BTC pairs settle off-protocol and most of their\nmatches never complete. A dispenser's price is set by its operator, so\n`spread_ratio` is published beside the rate. `from_block` states exactly what\nthe window covers. null is a real answer here: too few prints returns\n`rate: null` with the reason, never a number off a handful of trades.","operationId":"rates_api_rates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RatesEnvelope"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/pool/swaps":{"get":{"summary":"Pool Swaps","description":"The AMM swap tape — every POOL_MATCH, newest first, with full txids.\nUnfiltered by default. `asset_a`/`asset_b` filter by pair server-side, with\nthe same names and per-column meaning as `/api/pool/reserves`, and `status`\nfilters by status; the applied filter is echoed in `filter`. `matching` is the\nsize of the filtered set and `has_more` says whether rows remain beyond\n`limit`.","operationId":"pool_swaps_api_pool_swaps_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"asset_a","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset A"}},{"name":"asset_b","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset B"}},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoolSwapsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/pool/deposits":{"get":{"summary":"Pool Deposits","description":"Liquidity added to an AMM pool — every valid NEW_POOL_DEPOSIT, newest first.\nOnly `status = 'valid'` rows are presented (the archive keeps every row the\nchain produced), and `matching` counts that same valid set. `asset_a`/`asset_b`\nfilter server-side as on `/api/pool/reserves`; the applied filter is echoed in\n`filter`.","operationId":"pool_deposits_api_pool_deposits_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"asset_a","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset A"}},{"name":"asset_b","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset B"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoolDepositsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/pool/withdrawals":{"get":{"summary":"Pool Withdrawals","description":"Liquidity removed from an AMM pool — every valid NEW_POOL_WITHDRAWAL, newest\nfirst. The LP quantity is read from `quantity_destroyed`, else\n`quantity_burned`, else null (never 0); `lp_quantity_field` says which. Same\n`status = 'valid'` rule, `matching` count and `asset_a`/`asset_b` filter as\n`/api/pool/deposits`.","operationId":"pool_withdrawals_api_pool_withdrawals_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"asset_a","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset A"}},{"name":"asset_b","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset B"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoolWithdrawalsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/book":{"get":{"summary":"Book","description":"Resting DEX orders, reconstructed from this archive. An order consumed down to\nnothing is excluded even when its last update still says `open`.\n\nPagination is a keyset cursor, not an offset: pass the previous page's\n`next_cursor` (`<opened_block>:<event_index>`) as `before_cursor`. The cursor\nis the sort key itself, so a fill between two pages cannot make a reader skip a\nrow. `next_cursor` is null on the last page and `has_more` says the same. Pages\nare exact within one `as_of_block`; if it changes mid-walk the book itself\nchanged, so re-page for an exact snapshot.\n\n`total_open` is the whole book and ignores the filters; `matching` is the\nfiltered set. Both are the same on every page.","operationId":"book_api_book_get","parameters":[{"name":"give_asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"asset the maker is selling","title":"Give Asset"},"description":"asset the maker is selling"},{"name":"get_asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"asset the maker wants","title":"Get Asset"},"description":"asset the maker wants"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"before_cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"cursor: page on from the previous response's next_cursor","title":"Before Cursor"},"description":"cursor: page on from the previous response's next_cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/trades":{"get":{"summary":"Trades","description":"The DEX fill tape — every ORDER_MATCH, newest first. Taker-side convention:\n`forward_asset` is what the taker received, i.e. what the maker gave. Both\ntransaction hashes of a fill are always returned.\n\nThe filter parameter is `asset` (either side of the pair). Unknown query\nparameters are ignored, so a misspelled filter returns the unfiltered tape;\n`/api/candles` spells the same idea `base`/`quote`.\n\nThe whole matching set is not counted by default: `has_more` is exact, and for\n`matching` null means the page was truncated and the total was not counted\n(never `limit` passed off as a total). Pass `count_total=true` to have it\ncounted.","operationId":"trades_api_trades_get","parameters":[{"name":"asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"either side of the pair","title":"Asset"},"description":"either side of the pair"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"count_total","in":"query","required":false,"schema":{"type":"boolean","description":"also count the whole matching set into `matching`; off by default","default":false,"title":"Count Total"},"description":"also count the whole matching set into `matching`; off by default"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TradesEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/art/{kind}/{asset}":{"get":{"summary":"Art Object","description":"The mirrored artwork for one asset — our copy, not xcp.fun's. Served only while\nthe art mirror is switched on. Four distinguishable states, in the `X-Art-State`\nheader and in the body: `art` (200, the bytes) · `known_absent` ·\n`asked_no_bytes` · `never_asked` (404 each). A failure on our side is 503\n`archive_error` and is never reported as absence.","operationId":"art_object_art__kind___asset__get","parameters":[{"name":"kind","in":"path","required":true,"schema":{"type":"string","description":"icon | full","title":"Kind"},"description":"icon | full"},{"name":"asset","in":"path","required":true,"schema":{"type":"string","description":"the Counterparty asset name","title":"Asset"},"description":"the Counterparty asset name"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"not_priced","sellable":false,"price":null,"note":"this route has NO row in the price catalog. Not free — unpriced. See /api/pricing."}}},"/api/coverage":{"get":{"summary":"Coverage","description":"What this archive holds, per event type — and what it does not claim.\n\nNo outbound call is made. Every on-chain figure is what a node reported at a\nrecorded time, never a live count: the backfill's witness\n(`completeness_witness_count`, dated by `completeness_witness_at`), the figure\nthe backfill recorded (`on_chain_at_backfill`), or the newest census\n(`on_chain_at_last_census`, dated by `census_observed_at`). The live\ncomparison against a node runs separately, weekly.\n\nHolding rows of a type is not a claim that the type is complete. The live\npoller takes every type for every block it completes, and `poller_runs` lists\neach contiguous run of those blocks. Historical completeness is claimed only for\ntypes with a finished backfill; `historical_complete` is true only when that\nclaim carries a witness count the archive still meets, and `claim` states each\nrow's position in words. `completeness_witness_node` names the node the witness\ncame from.\n\n`last_reverify: null` means we hold NO RECORDED VERDICT — not that no re-check\nwas attempted. Among the types claiming completeness, a `completeness_witness_at`\nbehind the freshest one marks a type whose last re-check did not refresh its\nwitness.\n\n`mempool_this_node_partial` is one node's partial view of its own mempool,\ncaptured from block 962,672 (2026-08-16) onward; mempool state before that was\nnever recorded and cannot be recovered by anyone. `art_mirror_xcp_fun` is a\ndated copy of a third party's server. A null section with a non-null `*_error`\nis a count we could not take, never a zero.\n\nCached on wall-clock time, because the backfill moves these figures without\nmoving the archive head; `as_of_block` dates the build.","operationId":"coverage_api_coverage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/coverage/block/{height}":{"get":{"summary":"Coverage Block","description":"Was block N covered by this archive — and if not, what is missing?\n\nEvery height gets an answer, including heights never fetched and heights above\nthe chain tip; there is no 404, because \"no row\" would read the same as \"not\ncovered\", \"unknown\" and \"does not exist\". The states:\n  * `full`    — the poller fetched this block whole and committed every event.\n  * `partial` — covered for some event types only; `missing_types` names the\n                rest. Normal for pre-poller history.\n  * `none`    — nothing here is covered; the absence of an event at this height\n                means nothing about the chain.\n  * `suspect` — a block row exists but its bookkeeping contradicts the rows\n                present. Never reported as covered.\n  * `unknown` — could not tell which types are covered at this height, because\n                the per-type rollup is out of step with the archive. The\n                block-level fields (`poller_row`, `poller_complete`,\n                `event_count_claimed`, `events_held`) are still exact, and\n                `reason` says why.\n\n`missing_types: null` is not an empty list: with `coverage` anything but\n`unknown` it means no type census is recorded, so what is missing cannot be\nnamed; with `coverage: \"unknown\"` it means coverage could not be determined.\n`types_covered_basis` says which on every response. `[]` always means we can\nname it and nothing is missing.","operationId":"coverage_block_api_coverage_block__height__get","parameters":[{"name":"height","in":"path","required":true,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"block height; any height, including ones above this archive's reach","title":"Height"},"description":"block height; any height, including ones above this archive's reach"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoverageBlockEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/verify/{tx_hash}":{"get":{"summary":"Verify","description":"The raw archived events for one transaction hash, unmodified, so a reader can\ncheck our record against the chain. The hash must be the full 64 hexadecimal\ncharacters; anything else is refused with 400 rather than reported as a miss.\n\nOn a well-formed hash we do not hold, `found: false` cannot by itself separate\n\"the block was covered and this transaction is not in it\" from \"that block was\nnever covered\"; `/api/coverage/block/{height}` answers the second question.","operationId":"verify_api_verify__tx_hash__get","parameters":[{"name":"tx_hash","in":"path","required":true,"schema":{"type":"string","title":"Tx Hash"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max events in one page for this transaction","default":1000,"title":"Limit"},"description":"max events in one page for this transaction"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2147483647,"minimum":0,"description":"events to skip — the page cursor","default":0,"title":"Offset"},"description":"events to skip — the page cursor"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/gaps":{"get":{"summary":"Gaps","description":"Heights with no block row, from any writer, inside the stated window. The\nscanned range (`from`, `to`, `scanned_blocks`) is part of the answer: an empty\n`[]` means we looked over exactly that range and found no gap, which a reader can\ntell apart from a narrower scan. `truncated: true` means `limit` capped the list and\nmore gaps may exist. A block row means the block was ingested; it says nothing\nabout which event types are covered — that is `/api/coverage`.","operationId":"gaps_api_gaps_get","parameters":[{"name":"from","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2147483647,"minimum":0},{"type":"null"}],"description":"lowest height to scan","title":"From"},"description":"lowest height to scan"},{"name":"to","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":2147483647,"minimum":0},{"type":"null"}],"description":"highest height to scan","title":"To"},"description":"highest height to scan"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"description":"max gaps returned","default":50,"title":"Limit"},"description":"max gaps returned"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GapsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/mempool":{"get":{"summary":"Mempool","description":"This node's unconfirmed sightings — mints first.\n\nCapture began at block 962,672 (2026-08-16). Mempool state before that was\nnever recorded and cannot be recovered by anyone — a mempool is never written\nto the chain — so an empty answer about earlier blocks is a statement about our\ncoverage, not about what was pending. From then on capture is continuous, and\n`observed` publishes the span covered.\n\nThis is a partial view of one node's mempool and has not been compared with any\nother node's. `outcome: null` means still in the mempool as of `last_seen_at`.\nA pending mint is an attempt: `valid` comes from the node's own status string\nand is null when the node did not say.","operationId":"mempool_api_mempool_get","parameters":[{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"filter live rows to one event type","title":"Event"},"description":"filter live rows to one event type"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"max live rows returned","default":50,"title":"Limit"},"description":"max live rows returned"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MempoolEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/metrics":{"get":{"summary":"Metrics","description":"Prometheus text exposition of the archive's health, built from the same figures\nas `/api/health`. There is no JSON envelope: the `opreturn_last_block` gauge is\nthe archive head, and `opreturn_poll_age_seconds` and `opreturn_lag_blocks`\ndate it.","operationId":"metrics_api_metrics_get","responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/candles":{"get":{"summary":"Candles","description":"OHLCV from this archive, over its full history.\n\n`venue` selects DEX or AMM; the two are separate venues and are never combined.\nPrices are execution prices, fee-inclusive — not an AMM pool's marginal spot\nprice — and `price_basis` says so.\n\nPair orientation: the quote is the lower-ranked asset (XCP, then BTC, then\neverything else), so a pair asked the other way round is legitimately empty.\nOn an empty answer `absence_means` says whether the reversed pair has priced\nfills in this archive. It does not claim whether an asset exists.","operationId":"candles_api_candles_get","parameters":[{"name":"base","in":"query","required":true,"schema":{"type":"string","description":"base asset, e.g. PEPECASH","title":"Base"},"description":"base asset, e.g. PEPECASH"},{"name":"quote","in":"query","required":false,"schema":{"type":"string","description":"quote asset, e.g. XCP","default":"XCP","title":"Quote"},"description":"quote asset, e.g. XCP"},{"name":"venue","in":"query","required":false,"schema":{"type":"string","description":"DEX or AMM — never combined","default":"DEX","title":"Venue"},"description":"DEX or AMM — never combined"},{"name":"tf","in":"query","required":false,"schema":{"type":"string","description":"TIME buckets 5m, 15m, 1h, 4h, 1d, 1w — or TRADING-BLOCK buckets 1b, 6b, 12b, 36b, 144b, 576b, 1008b. ⚠️ CHANGED 2026-09-06, IN PLACE: `Nb` groups N blocks THAT CARRIED A PRICED FILL for this pair and venue — it is NOT N consecutive chain blocks, which is what it meant before that date. `12b` therefore always holds twelve blocks that traded, however far apart on chain they sit; the bar reaches from `first_block` to `last_block` and that distance is normally much larger than N (7.5% of chain blocks carried a fill on the pair this was measured against). `bucket_basis` reads `trading_block` on this basis, and the newest bar may be PARTIAL — `trading_blocks` on each row says how many it actually holds. The two bases are different QUESTIONS, not two spellings of one scale: a duration holds a variable number of blocks and a block count holds a variable duration, so no block key is an alias for a time key and none may be converted to one.","default":"1d","title":"Tf"},"description":"TIME buckets 5m, 15m, 1h, 4h, 1d, 1w — or TRADING-BLOCK buckets 1b, 6b, 12b, 36b, 144b, 576b, 1008b. ⚠️ CHANGED 2026-09-06, IN PLACE: `Nb` groups N blocks THAT CARRIED A PRICED FILL for this pair and venue — it is NOT N consecutive chain blocks, which is what it meant before that date. `12b` therefore always holds twelve blocks that traded, however far apart on chain they sit; the bar reaches from `first_block` to `last_block` and that distance is normally much larger than N (7.5% of chain blocks carried a fill on the pair this was measured against). `bucket_basis` reads `trading_block` on this basis, and the newest bar may be PARTIAL — `trading_blocks` on each row says how many it actually holds. The two bases are different QUESTIONS, not two spellings of one scale: a duration holds a variable number of blocks and a block count holds a variable duration, so no block key is an alias for a time key and none may be converted to one."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":5000,"minimum":1,"default":500,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CandlesEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/reorgs":{"get":{"summary":"Reorgs","description":"Forks this archive observed the chain take back — the reorg log. A public\nCounterparty node forgets a reorged-out block; this archive copies the losing\nchain's events and block envelopes out first, and this route reads them back.\n\nThree answers, kept apart:\n  1. `forks: []`   — we looked. The log exists and no fork has been observed\n                     since `preservation.since`.\n  2. `forks: null` — we could not look, and `preservation.reason` says why.\n                     Still HTTP 200, because that is a stable fact, not a\n                     retryable one.\n  3. HTTP 503      — the database could not be asked at all. Try again.\n\nEven `forks: []` is not \"there have been no reorgs\": a reorg before\n`preservation.since`, or one that happened while the poller was stopped, left\nnothing here. `absence_means` carries that sentence in the response.","operationId":"reorgs_api_reorgs_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"max forks returned","default":50,"title":"Limit"},"description":"max forks returned"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReorgsEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/launches/deadline-rewrites":{"get":{"summary":"Deadline Rewrites","description":"Launches whose soft-cap deadline the node rewrote — and what it was before.\n\nThree answers, kept apart:\n  1. `rewrites: []`   — we looked, and no launch in this archive has a\n                        deadline-rewrite event.\n  2. `rewrites: null` — we could not look, and `availability.reason` says why.\n                        Still HTTP 200: a stable fact, not a retryable one.\n  3. HTTP 503         — the database could not be asked at all. Try again.\n\nEven `rewrites: []` is scoped to the update events this archive captured; a\nrewrite before our coverage, or while the poller was stopped, left nothing\nhere. `absence_means` carries that in the response.","operationId":"deadline_rewrites_api_launches_deadline_rewrites_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"max rewrites returned","default":50,"title":"Limit"},"description":"max rewrites returned"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeadlineRewritesEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/venue-gap":{"get":{"tags":["metrics"],"summary":"AMM vs DEX price, sampled ~every 10 minutes, side by side","description":"The venue-gap series: DEX and AMM price samples for a pair, collected roughly\nevery 10 minutes, side by side.","operationId":"venue_gap_api_venue_gap_get","parameters":[{"name":"base","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Base asset, exact.","title":"Base"},"description":"Base asset, exact."},{"name":"quote","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Quote asset, exact.","title":"Quote"},"description":"Quote asset, exact."},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only samples at or after this instant.","title":"Since"},"description":"Only samples at or after this instant."},{"name":"until","in":"query","required":false,"schema":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"description":"Only samples at or before this instant.","title":"Until"},"description":"Only samples at or before this instant."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VenueGapEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/block-times":{"get":{"tags":["metrics"],"summary":"block height -> Bitcoin timestamp, the only such map below block 952,800","description":"Block height → Bitcoin timestamp — the map that puts deep history on a time\naxis. The dense era from block 952,800 is fully mapped; the sparse era below it\nis not, and `range_coverage` on every response states, dated, how much of the\nrequested range is mapped. Some blocks that contain events have no timestamp: a\ntime-axis chart over deep history drops or misplaces them, a block-axis chart\ndoes not. `include_event_gap=true` counts them for the requested range.","operationId":"block_times_api_block_times_get","parameters":[{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":280312,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"asc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"minimum":1,"default":5000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000000,"minimum":0,"default":0,"title":"Offset"}},{"name":"include_event_gap","in":"query","required":false,"schema":{"type":"boolean","description":"Also count event-bearing blocks in range with NO timestamp. Refused above 50000 blocks; see method.","default":false,"title":"Include Event Gap"},"description":"Also count event-bearing blocks in range with NO timestamp. Refused above 50000 blocks; see method."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BlockTimeEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/launch/{tx_hash}/mint-curve":{"get":{"tags":["metrics"],"summary":"mints per block for one fair-mint launch","description":"The shape of a mint: how fast, in which blocks, by how many addresses. Only\nvalid mints are counted, so a node's raw `NEW_FAIRMINT` count for the same\nlaunch is higher. The rejected attempts are served by `/api/mints/failed`,\nwhose `census` gives the share; they carry no asset and no launch hash, so none\ncan be attributed to a launch or added to this curve.","operationId":"mint_curve_api_launch__tx_hash__mint_curve_get","parameters":[{"name":"tx_hash","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-fA-F]{64}$","description":"The FAIRMINTER announcement tx_hash, 64 hex.","title":"Tx Hash"},"description":"The FAIRMINTER announcement tx_hash, 64 hex."},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"asc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":20000,"minimum":1,"default":5000,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintCurveEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/pool/reserves":{"get":{"tags":["metrics"],"summary":"per-block AMM reserve series back to each pool's opening block","description":"The per-block AMM reserve series, back to each pool's opening block. The AMM\nopened at block 952,800, which is also the archive's dense floor, so the series\nis complete from each pool's first block. `via` distinguishes a pool's creation\n(`OPEN_POOL`) from a later change (`POOL_UPDATE`).","operationId":"pool_reserves_api_pool_reserves_get","parameters":[{"name":"asset_a","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset A"}},{"name":"asset_b","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset B"}},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PoolReserveEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/launches/conformance":{"get":{"tags":["metrics"],"summary":"which fair-mint launches follow the XCP-69 standard, as a filter","description":"Which fair-mint launches follow the XCP-69 standard, as a filter. Three states,\nnot two: `conformant`, `nonconformant` and `unknown`. An `unknown` launch is one\nwhose announcement the node itself rejected, so it carries no asset and no\nparameters to judge; `conformance=unknown` returns exactly those.\n`include_mint_rollup=true` adds `mint_count`, `minters` and `xcp_raised`; it is\noff by default.","operationId":"launches_conformance_api_launches_conformance_get","parameters":[{"name":"conformance","in":"query","required":false,"schema":{"type":"string","pattern":"^(all|conformant|nonconformant|unknown)$","default":"all","title":"Conformance"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}},{"name":"include_mint_rollup","in":"query","required":false,"schema":{"type":"boolean","description":"Also return mint_count/minters/xcp_raised. Off by default.","default":false,"title":"Include Mint Rollup"},"description":"Also return mint_count/minters/xcp_raised. Off by default."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConformanceEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/dispensers":{"get":{"tags":["metrics"],"summary":"every dispenser opened, with its BTC price and inventory at open","description":"Every dispenser ever opened, with its BTC price and inventory at open — a log of\nopenings, not a list of what is open now (that is `/api/dispensers/open`).\nDispensers that have since closed are still returned. The history is complete\nfrom the dispenser family's first block; the response's `caveats` and\n`coverage` state its limits.","operationId":"dispensers_api_dispensers_get","parameters":[{"name":"asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset"}},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope_DispenserOpen_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/dispenses":{"get":{"tags":["metrics"],"summary":"every dispenser purchase: who bought, what, and for how much BTC","description":"Every dispenser purchase: who bought, what, and for how much BTC — complete\nfrom the dispenser family's first block.\n\n`dispenser_tx_hash` returns one dispenser's whole drain tape. Prefer a block\nwindow (`from_block`/`to_block`) when paging deep or filtering;\n`coverage.block_range_returned` on any page gives the window for the next one.\n`result_state` distinguishes `empty_no_match`, `empty_source` and\n`empty_past_end`.\n\nWhat a DISPENSE row does not carry: the dispenser's own terms (rate, escrow,\ninventory) — those are on `/api/dispensers`. No node is consulted at request\ntime; `freshness.as_of_block` is the extent of what this answer covers.","operationId":"dispenses_api_dispenses_get","parameters":[{"name":"asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset"}},{"name":"dispenser_tx_hash","in":"query","required":false,"schema":{"anyOf":[{"type":"string","minLength":64,"maxLength":64,"pattern":"^[0-9a-fA-F]{64}$"},{"type":"null"}],"description":"ONE dispenser's whole drain tape, by the tx_hash of the OPEN_DISPENSER that created it. Case-insensitive; matched lowercased. Pair it with from_block/to_block when you know the window.","title":"Dispenser Tx Hash"},"description":"ONE dispenser's whole drain tape, by the tx_hash of the OPEN_DISPENSER that created it. Case-insensitive; matched lowercased. Pair it with from_block/to_block when you know the window."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope_DispenseRow_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/dispensers/open":{"get":{"tags":["metrics"],"summary":"dispensers standing RIGHT NOW, with what is actually left in them","description":"Which dispensers are open now, and how much is left in each.\n\nEvery row carries `open_evidence`: `confirmed_open` (the chain's latest word on\nthe dispenser says open) or `presumed_open` (we saw it open and have never seen\nit close — not the same as knowing it is open: a dispenser that has never been\ntouched emits nothing, so silence cannot tell a quiet dispenser apart from one\nwhose close we missed). The `census` block on every response carries\nthe live split. `give_remaining` does not include refills, so it is a lower\nbound. `coverage.block_floor` is the earliest block covered and\n`freshness.as_of_block` the newest.","operationId":"dispensers_open_api_dispensers_open_get","parameters":[{"name":"asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Asset"}},{"name":"evidence","in":"query","required":false,"schema":{"type":"string","pattern":"^(any|confirmed|presumed)$","default":"any","title":"Evidence"}},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DispenserOpenNowEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/mints/failed":{"get":{"tags":["metrics"],"summary":"fair-mint attempts the node REJECTED: the payer, the block and the reason","description":"Every fair-mint attempt the node recorded and rejected — with the reason, the\npayer and the block. `census` on every response gives the rejected share of all\n`NEW_FAIRMINT` rows. These attempts moved no XCP and are excluded from\n`/api/mints` and the mint curves.","operationId":"failed_mints_api_mints_failed_get","parameters":[{"name":"reason","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":512},{"type":"null"}],"description":"Exact match on the node's status string.","title":"Reason"},"description":"Exact match on the node's status string."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":1,"default":500,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedMintEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/mints/failed/reasons":{"get":{"tags":["metrics"],"summary":"why fair-mints failed, grouped by the node's verbatim reason string","description":"Why fair-mints failed, grouped by the node's verbatim reason string. The number\nof distinct strings grows with the number of assets people fail to mint, not\nwith the number of failure modes — see the response's `caveats`.","operationId":"failed_mint_reasons_api_mints_failed_reasons_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FailedMintReasonEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/orders":{"get":{"tags":["lifecycle"],"summary":"every order this archive holds, in whatever state it ended up in","description":"The full order lifecycle — every order this archive holds, in whatever state it\nended up in: filled, expired, cancelled, refused or open. (`/api/book` serves\nonly the resting ones.)\n\nFilters are exact matches. A block window (`from_block`/`to_block`) is the way\nto narrow a large request. An empty `status=` means no status filter, not a\nfilter on the empty string; `/api/orders/outcomes` lists the status strings.\n\nNot covered: orders below the archive's oldest event; an order's intermediate\nstates (only its opening event and its latest update are joined); its\ncounterparties (`/api/matches` for settlement state, `/api/trades` for the\nfills); and ORDER_FILLED events — status rests on ORDER_UPDATE.","operationId":"orders_api_orders_get","parameters":[{"name":"give_asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Exact asset the order gives.","title":"Give Asset"},"description":"Exact asset the order gives."},{"name":"get_asset","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Exact asset the order wants.","title":"Get Asset"},"description":"Exact asset the order wants."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"description":"Exact address that placed it.","title":"Source"},"description":"Exact address that placed it."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"description":"Exact status string. Pair it with from_block/to_block for a large range. An empty value (`?status=`) means no status filter, not a filter on the empty string. /api/orders/outcomes lists the strings.","title":"Status"},"description":"Exact status string. Pair it with from_block/to_block for a large range. An empty value (`?status=`) means no status filter, not a filter on the empty string. /api/orders/outcomes lists the strings."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"description":"Only orders opened at or after this block. A block window is the way to narrow a large request.","default":0,"title":"From Block"},"description":"Only orders opened at or after this block. A block window is the way to narrow a large request."},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":1000000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderStateEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}},"402":{"description":"⭐ PAYMENT REQUIRED, AND THE OFFER IS THE DISCOVERY. This is not an error: the body and the `PAYMENT-REQUIRED` header both carry a complete, signable x402 v2 offer, so a machine can pay and retry without asking a human anything. ⚠️ IT IS A PROPERTY OF THE REQUEST, NOT OF THE ROUTE. Current state at display size is FREE on every route here — that is what the website itself asks for — and only a HISTORY request is charged. The same URL answers 200 and 402 depending on what you asked it for. ⚠️ AND IT IS REACHABLE ONLY WHILE A RAIL IS USABLE. With none configured a priced request is REFUSED 503 rather than offered — see this operation's 503, `reason: no_rail_configured`. A consumer that has never seen a 402 has not proved it cannot arrive.","headers":{"PAYMENT-REQUIRED":{"description":"The same offer, base64-encoded. ⛔ THIS IS THE NORMATIVE COPY — x402 v2 puts the offer in the header and calls the body a server implementation concern. Both are rendered from one method so they cannot disagree.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentOffer"}}}}},"x-price":{"access":"priced","sellable":true,"currency":"USD","unit":"request","metered":false,"price":{"micro_usd":10000,"display":"$0.01","per":"1 request","means":"the total charge for one call to this route"},"free_rows":200,"ceiling":"bounded by the route's own limits on one response; the largest response has not been measured in bytes.","question":"Every order this archive has ever seen, and how it ended — filled, expired, cancelled, or still resting. NOT the live book: that is /api/book. This is the history, and nobody else publishes it."}}},"/api/orders/outcomes":{"get":{"tags":["lifecycle"],"summary":"how every order ended — and Counterparty's own words for the refusals","description":"How every order ended — and Counterparty's own reason strings for the refusals.\nA refused order is not an error in this archive: the chain recorded it with a\nreason. Per status string the response gives the count and the earliest and\nlatest block; the counts move every block and the response carries its own.\n\nThe whole-archive census is served from a cache that keeps the last good\ntotals while a rebuild runs or fails. Read `stale_fallback` and `rows_age_s` on\nevery response: they tell a current answer from a labelled older one, and every\n`measured_at` and `as_of_block` on the response is derived from the same age.\nWith `from_block` the census covers that window only and is built for the\nrequest, so it is never a stale fallback.\n\nNot covered: WHICH orders were refused (use `/api/orders?status=<the string>`);\ntransactions the chain never parsed into an order; orders below the archive's\noldest event; and whether a reason still occurs today (`last_block` per reason\nanswers that).","operationId":"order_outcomes_api_orders_outcomes_get","parameters":[{"name":"rejected_only","in":"query","required":false,"schema":{"type":"boolean","description":"Return only the 'invalid:' rejection reasons. The census is computed over ALL classes either way, so the denominators do not move when you filter.","default":false,"title":"Rejected Only"},"description":"Return only the 'invalid:' rejection reasons. The census is computed over ALL classes either way, so the denominators do not move when you filter."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"description":"Only orders opened at or after this block. A windowed census is built for the request, so it is never a stale fallback; omit this for the whole-archive census, which is served from a cache with a labelled last-good fallback.","default":0,"title":"From Block"},"description":"Only orders opened at or after this block. A windowed census is built for the request, so it is never a stale fallback; omit this for the whole-archive census, which is served from a cache with a labelled last-good fallback."},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderOutcomeEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/matches":{"get":{"tags":["lifecycle"],"summary":"order matches that were struck and then fell through","description":"Order matches that were struck and then fell through. A match settles\n`completed` or `expired` — struck, then fallen through, typically because the\nBTC leg was never paid.\n\nThis view holds only the matches that ever produced an ORDER_MATCH_UPDATE; a\nmatch that settled on the spot is absent. Read `census.matches_total` before\ncomputing any rate. Rows carry an id and a state only: the detail is in\n`/api/trades` under the same `match_id`, and `maker_tx`/`taker_tx` are the two\ntxids. Why a particular match expired is not recorded.","operationId":"matches_api_matches_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"'expired' or 'completed'. Omit for both.","title":"Status"},"description":"'expired' or 'completed'. Omit for both."},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"asc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchStateEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/launches/status":{"get":{"tags":["lifecycle"],"summary":"per-launch status AND the block that status landed in","description":"When a launch reached its status: the newest FAIRMINTER_UPDATE per launch and\n`status_block`, the block it landed in — and only statuses that came from such\nan update, never the announce-time one. Launches with no status update are not\nlisted (`census` says how many); their announce-time status is on\n`/api/launches`. This is the newest update only, not the history. An update\ncarrying no status key leaves `status` null; deadline rewrites are published on\n`/api/launches/deadline-rewrites`.","operationId":"launch_status_api_launches_status_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Exact status, e.g. 'closed'.","title":"Status"},"description":"Exact status, e.g. 'closed'."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":1000,"minimum":1,"default":200,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LaunchStatusEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/reorgs/events":{"get":{"tags":["lifecycle"],"summary":"the individual events a reorg took back, with both block hashes","description":"The per-event evidence behind a reorg — both block hashes, side by side, for\nevery event this archive copied out before the losing chain was deleted.\n`/api/reorgs` answers which forks we saw; this answers which events were taken\nback. `census` separates re-fetches of a block whose hash never changed from\nrows that genuinely diverged.\n\nNot covered: reorgs before preservation began or while the poller was stopped\n(an empty answer is about this node's observation window, never about the\nchain); the winning block's events (those are the archive's normal contents);\nand why a fork happened.","operationId":"reorg_events_api_reorgs_events_get","parameters":[{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"description":"Exact event type, e.g. NEW_BLOCK.","title":"Event"},"description":"Exact event type, e.g. NEW_BLOCK."},{"name":"from_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":0,"title":"From Block"}},{"name":"to_block","in":"query","required":false,"schema":{"type":"integer","maximum":100000000,"minimum":0,"default":100000000,"title":"To Block"}},{"name":"include_params","in":"query","required":false,"schema":{"type":"boolean","description":"Also return each event's full payload. OFF by default because a large fork would return a large payload per row; null means NOT REQUESTED, never empty.","default":false,"title":"Include Params"},"description":"Also return each event's full payload. OFF by default because a large fork would return a large payload per row; null means NOT REQUESTED, never empty."},{"name":"order","in":"query","required":false,"schema":{"type":"string","pattern":"^(asc|desc)$","default":"desc","title":"Order"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrphanEventEnvelope"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"503":{"description":"The request could not be answered. ⚠️ `{\"detail\": …}` is the DATABASE branch — `database error: …` names the kind of database failure, and a query that ran out of time is RETRYABLE, ideally with a narrower block window; `database pool not initialised` means this process is not connected to its database yet. The `{\"error\", \"detail\", \"source\"}` branch is the PAYMENT GATE declining to SELL (a refusal to take money, not a refusal of the caller) and can only occur while payment enforcement is on. ⚠️ BRANCH ON `reason` — BUT TEST FOR ITS ABSENCE FIRST. TWO of the six 503 branches build a body with NO `reason` key at all — `unresolved_resource` and `not_in_catalog` — so a missing `reason` means 'unclassified refusal', never a default. The values that DO reach a body are `stale_data`, `freshness_unknown`, `params_not_supplied`, `needs_decision`, and whatever rail failure the catch-all carries (`no_rail_registered` and its siblings). ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}},"/api/pricing":{"get":{"summary":"The price list: what is available, and what it costs","description":"The table of contents: every route, what it answers, what it costs.\n\nFree, and it always will be — a price list you have to buy is not a\nprice list. Generated from the price catalog on every request, so it\ncannot drift from the 402 offers built from the same rows.","operationId":"pricing_api_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"503":{"description":"The request could not be answered. ⚠️ THIS OPERATION CANNOT FAIL THE WAY THE OTHERS DO — it is free in the catalog and its handler reaches no database call, so there is no `{\"detail\": …}` branch here. The ONE 503 it can serve is the PAYMENT GATE refusing BEFORE the resource is identified: the gate answers UNRESOLVED when route matching itself fails, and that branch runs ahead of — and independently of — which route was asked for. It can only occur while payment enforcement is on. ⚠️ A 503 NEVER MEANS THE ARCHIVE IS WRONG; it means this question was not answered.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/PaymentRefusal"}]}}}}},"x-price":{"access":"free","sellable":false,"price":null}}}},"components":{"schemas":{"BlockEventsEnvelope":{"properties":{"block":{"anyOf":[{"$ref":"#/components/schemas/BlockRow"},{"type":"null"}]},"complete":{"type":"boolean","title":"Complete","description":"The `blocks.complete` INGEST column: true only once EVERY event of this block was committed, in the same transaction, so a crash mid-block leaves it false and the block is simply re-ingested. It is an ARCHIVE HONESTY flag — false means an absent event type here is NOT evidence the chain lacks it. ⛔ IT IS NOT THE `Envelope.complete` PAGING FLAG, which is a claim about THE PAGE and pairs with `next_offset`. Same word, different field, different contract: this one is about the block, that one is about the response. Tell them apart by `result_state`: the paging flag always sits beside one, and this flag never does. Do NOT use `next_offset` for it — GET /api/blocks/{height}/events pages this block's events, and its body carries `truncated` and `next_offset` beside this flag, so `complete: true` with `truncated: true` is a normal answer: a block archived whole whose events did not fit in one page. ⚠️ Promoted from `block.complete`, and FALSE when no `blocks` row exists at all — which is the normal shape for backfilled history, where `block` is null beside a NON-EMPTY `result`. So `false` here means 'not known to be whole', never 'known to be partial'; read `coverage`, `coverage_reason` and `missing_types` for what this archive actually claims at this height."},"block_absent_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Block Absent Reason"},"result":{"items":{"$ref":"#/components/schemas/BlockScopedEventRow"},"type":"array","title":"Result"},"coverage":{"type":"string","title":"Coverage"},"coverage_reason":{"type":"string","title":"Coverage Reason"},"missing_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Missing Types"},"coverage_note":{"type":"string","title":"Coverage Note"}},"type":"object","required":["block","complete","block_absent_reason","result","coverage","coverage_reason","missing_types","coverage_note"],"title":"BlockEventsEnvelope","description":"GET /api/blocks/{height}/events.\n\n`block` is null whenever no `blocks` row exists — which legitimately co-occurs\nwith a NON-EMPTY `result` for backfilled history.\n`missing_types` is tri-state: null = the type universe is unknown, [] = nothing\nmissing."},"BlockRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"block_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Block Hash"},"block_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Block Time"},"event_count":{"type":"integer","title":"Event Count"},"complete":{"type":"boolean","title":"Complete","description":"The `blocks.complete` INGEST column: true only once EVERY event of this block was committed, in the same transaction, so a crash mid-block leaves it false and the block is simply re-ingested. It is an ARCHIVE HONESTY flag — false means an absent event type here is NOT evidence the chain lacks it. ⛔ IT IS NOT THE `Envelope.complete` PAGING FLAG, which is a claim about THE PAGE and pairs with `next_offset`. Same word, different field, different contract: this one is about the block, that one is about the response. Tell them apart by `result_state`: the paging flag always sits beside one, and this flag never does. Do NOT use `next_offset` for it — GET /api/blocks/{height}/events pages this block's events, and its body carries `truncated` and `next_offset` beside this flag, so `complete: true` with `truncated: true` is a normal answer: a block archived whole whose events did not fit in one page."}},"type":"object","required":["block_index","block_hash","block_time","event_count","complete"],"title":"BlockRow","description":"One archived block. `block_hash` and `block_time` are nullable."},"BlockScopedEventRow":{"properties":{"event_index":{"type":"integer","title":"Event Index"},"event":{"type":"string","title":"Event"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"The node's raw JSONB payload for this event, served whole and deliberately untyped — see `EventRow.params` for why. ⛔ `BLOCK_PARSED.params.messages_hash` IS NODE-LOCAL AND TIME-VARYING — it is NOT a consensus value and it is NOT comparable across nodes. A mismatch on this one key against another node is NOT evidence that this archive is wrong; it is a property of the field. Compared over 22 strided heights (280,000-963,400, 364 event rows, field by field, two independent operators): the event SETS agree everywhere, and so do the two CONSENSUS hashes `ledger_hash` and `txlist_hash` — `messages_hash` is the ONLY key both nodes emit and disagree on. In a whole-population count over blocks 952,800-963,459, 7,407 of those 10,660 BLOCK_PARSED rows held a value NO rostered node served, not even the same build that produced it. ⇒ Do not diff it, do not reconcile against it, and do not derive from it: nothing in this API reads it. ⚠️ AND THIS ROW CANNOT TELL YOU WHICH BUILD PRODUCED THE VALUE. Provenance is RECORDED PER ROW IN THE ARCHIVE — `events.source_node`, `events.node_version`, `events.node_commit` name the node and the build that wrote a row, where they were recorded (rows ingested before these columns existed carry none). Which build wrote a row is recorded, but it is NOT PUBLISHED HERE: `node_version` / `node_commit` are published by no route at all, so nothing in this response attributes this value to a build. The only provenance published anywhere in this API is `MempoolEnvelope.source_node`, `OrphanEvent.source_node`. On a row of the orphaned-event log, the `source_node` beside this payload is that row's own, copied from the archive when the event was orphaned (null where none was recorded), and it names the NODE, never the build; an archived event row outside that log carries no provenance field at all, and the mempool's `source_node` describes mempool sightings, not archived events. Publishing the build per row would be a response-shape change, which this API has declined, so the honest answer is that the build attribution exists in the archive and you cannot read it from here. ⇒ That is the reason to disregard a mismatch on this key rather than to reconcile it."}},"type":"object","required":["event_index","event","tx_hash","params"],"title":"BlockScopedEventRow","description":"An event inside one block.\n\n⚠️ NOT `EventRow`: it carries no `block_index`, because the block is the\nroute's own parameter."},"BlockTimeEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/BlockTimePoint"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"range_coverage":{"$ref":"#/components/schemas/BlockTimeRangeCoverage"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","range_coverage"],"title":"BlockTimeEnvelope"},"BlockTimePoint":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"block_time":{"type":"string","format":"date-time","title":"Block Time"},"source":{"type":"string","title":"Source","description":"'params' (read off an event) or 'envelope' (the block envelope)."}},"additionalProperties":false,"type":"object","required":["block_index","block_time","source"],"title":"BlockTimePoint"},"BlockTimeRangeCoverage":{"properties":{"from_block":{"type":"integer","title":"From Block","description":"Echoes your request."},"to_block":{"type":"integer","title":"To Block","description":"Echoes your request, unclamped."},"to_block_effective":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"To Block Effective","description":"The upper bound the COVERAGE ARITHMETIC actually used: min(to_block, archive head). null when the head could not be read. ⚠️ The default to_block is a parameter CEILING (100,000,000), not a chain fact; computing coverage against it published 99.4 million non-existent blocks as a measured number. The clamp is echoed rather than applied silently, because an unreported bound is a new way to be undated."},"clamped_to_archive_head":{"type":"boolean","title":"Clamped To Archive Head","description":"True when to_block_effective < to_block, i.e. you asked past the head."},"blocks_in_range":{"$ref":"#/components/schemas/Measured_int_"},"blocks_resolved":{"$ref":"#/components/schemas/Measured_int_","description":"Counted over the range you ASKED for, not the clamped one — this is what the archive actually holds, and clamping it would hide rows."},"blocks_unresolved":{"$ref":"#/components/schemas/Measured_int_"},"event_bearing_blocks_unresolved":{"$ref":"#/components/schemas/Measured_int_","description":"Event-bearing blocks in range with NO timestamp — these are the ones a time-axis chart drops. value may be null when not requested; method says so."}},"additionalProperties":false,"type":"object","required":["from_block","to_block","to_block_effective","clamped_to_archive_head","blocks_in_range","blocks_resolved","blocks_unresolved","event_bearing_blocks_unresolved"],"title":"BlockTimeRangeCoverage","description":"★ THE POINT OF THIS ROUTE. AS OF BLOCK 963,510 (2026-08-22) 45.6 percent of\nthe block span carries a timestamp (311,763 rows over 280,312-963,510), and a\ntime-axis chart over deep history silently drops the rest. Re-read\n`blocks_resolved` below; do not quote this sentence."},"BookEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/BookRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"matching":{"type":"integer","title":"Matching"},"total_open":{"type":"integer","title":"Total Open"},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Next Cursor"},"has_more":{"type":"boolean","title":"Has More"},"as_of_block":{"type":"integer","title":"As Of Block"},"archive_head":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Archive Head","description":"The archive head this answer was SERVED against — max(block_index) FROM blocks WHERE complete, the same expression /api/health publishes as `last_block`. It is read once, by the cache helper, in the same breath as the decision that chose these rows — not a second lookup that could have moved. NOT `ingest_health.chain_tip`: that is what the poller last saw the NODE's tip to be, and it diverges from the archive under catch-up and after a reorg. Declared optional in this schema; the handler always emits it."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when `as_of_block != archive_head`, i.e. these rows were NOT built at the current archive head: the build for it did not finish and you were served the last good book instead, or you were handed a concurrent build's rows from an earlier head. The status code cannot tell you this and neither can the latency: a stale answer can arrive quickly. ⛔ THIS IS THE CACHE-vs-ARCHIVE AXIS AND IS NOT `freshness.stale` / `stale_reason_codes`, which are stable API on the metrics surface and describe ARCHIVE-vs-CHAIN. A response can carry stale_fallback=true while the archive itself is level with the chain — that is the normal shape of this outage. ⚠️ AFTER A REORG the archive head DECREASES, so a fallback can publish `as_of_block` GREATER than `archive_head`: the flag is still true and a lag computed as archive_head - as_of_block is NEGATIVE. Declared optional in this schema; the handler always emits it."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route is REMEMBERED right now — the route is inside a labelled-fallback window and is declining to re-attempt for a cooldown. ⛔ THIS IS A DIFFERENT QUESTION FROM `stale_fallback` AND THE TWO COME APART. `stale_fallback` is a HEAD COMPARISON (are these rows at the current archive head?); this is DID-THE-BUILD-WORK. All four combinations are reachable, and the one that motivated this field is FRESH + FAILING: a request whose own build failed can publish `stale_fallback: false` with `as_of_block == archive_head`, because a concurrent build stored rows at the current head while it was failing. Without this field the response reads healthy in exactly that case. ⛔ It is NOT `freshness.stale` / `stale_reason_codes` either — those are ARCHIVE-vs-CHAIN. Three axes, three names. ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'."},"venue":{"type":"string","title":"Venue"},"units":{"type":"string","title":"Units"},"note":{"type":"string","title":"Note"},"source":{"type":"string","title":"Source"},"book_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Book Source","description":"Present only while this route is switched to read the `order_state` table. `order_state` when these rows came from that table, read in the same database snapshot as `as_of_block`; `v_order_open` when the switch is on but the table was REFUSED and the view served the answer instead, and `book_source_refusal` then says why. Absent entirely when the switch is off, which is the default."},"reconciled_head":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reconciled Head","description":"Present only while switched. The archive head at which the last reconcile run with verdict `passed` found `order_state` equal to the `v_order_state` view it replaces. This is the SECOND half of the stamp: `as_of_block` says which block the table is current as of, and this says how far that has been independently checked. Null when no run has ever passed or the table could not be read."},"reconciled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reconciled At","description":"Present only while switched. When that passed reconcile run was recorded (ISO 8601), so the age of the last agreement is readable without a second request. Null exactly when `reconciled_head` is null."},"book_source_refusal":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Book Source Refusal","description":"Present only while switched. Null when `order_state` served these rows. Otherwise `CODE: detail` naming the condition that withdrew the table (READ_ROLE_NOT_GRANTED, NO_ARCHIVE_HEAD, NO_PASSED_RUN, LAST_RUN_NOT_PASSED, AGREEMENT_TOO_OLD, HEAD_BELOW_CERTIFIED, HEAD_TOO_FAR_AHEAD, STREAK_TOO_SHORT, STREAK_SPAN_TOO_SHORT or READ_FAILED), and the rows are the view's, exactly as they would be with the switch off."}},"type":"object","required":["result","count","matching","total_open","next_cursor","has_more","as_of_block","venue","units","note","source"],"title":"BookEnvelope","description":"GET /api/book.\n\n`matching` is the number of open orders that match the give/get filter ACROSS\nALL PAGES — it does not shrink as you page; `count` is the number of rows on\nTHIS page. `matching` is ALWAYS an int here — unlike /api/trades, where it can\nbe null. `as_of_block` can be -1 when nothing is archived.\n\n⛔ The four source keys at the end exist only while the route is switched to\nread the `order_state` table, so a body served with the switch off — the\ndefault — does not carry them at all."},"BookRow":{"properties":{"tx":{"type":"string","title":"Tx"},"source":{"type":"string","title":"Source"},"give_asset":{"type":"string","title":"Give Asset"},"get_asset":{"type":"string","title":"Get Asset"},"give_quantity":{"type":"number","title":"Give Quantity"},"get_quantity":{"type":"number","title":"Get Quantity"},"give_remaining":{"type":"number","title":"Give Remaining"},"get_remaining":{"type":"number","title":"Get Remaining"},"give_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Give Divisible"},"get_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Get Divisible"},"give_quantity_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Give Quantity Normalized"},"get_quantity_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Get Quantity Normalized"},"give_remaining_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Give Remaining Normalized"},"get_remaining_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Get Remaining Normalized"},"expire_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expire Index"},"opened_block":{"type":"integer","title":"Opened Block"},"opened_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Opened At"}},"type":"object","required":["tx","source","give_asset","get_asset","give_quantity","get_quantity","give_remaining","get_remaining","give_divisible","get_divisible","give_quantity_normalized","get_quantity_normalized","give_remaining_normalized","get_remaining_normalized","expire_index","opened_block","opened_at"],"title":"BookRow","description":"One open order.\n\n`*_quantity_normalized` can be NON-NULL while the matching `*_divisible` flag is\nnull — they come from different sources and are NOT coupled.\n`expire_index` null means \"never expires\", not \"unknown\"."},"CandleRow":{"properties":{"t":{"type":"string","title":"T"},"o":{"type":"number","title":"O"},"h":{"type":"number","title":"H"},"l":{"type":"number","title":"L"},"c":{"type":"number","title":"C"},"v_base":{"type":"number","title":"V Base"},"v_quote":{"type":"number","title":"V Quote"},"n":{"type":"integer","title":"N"},"first_block":{"type":"integer","title":"First Block"},"last_block":{"type":"integer","title":"Last Block"},"bucket_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bucket Block","description":"TRADING-BLOCK BASIS ONLY: the bar's IDENTITY — the first block that traded in it. ⚠️ EQUAL TO `first_block` BY CONSTRUCTION, and that is a change: this used to be the ALIGNED start `(block_index / N) * N`, which could name a block that never traded. A bucket now BEGINS at its first trading block, so there is no aligned start to name. The invariants are bucket_block == first_block <= last_block, last_block - first_block + 1 >= trading_blocks, and trading_blocks <= bucket_blocks. Null on the time basis."},"bucket_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bucket Blocks","description":"TRADING-BLOCK BASIS ONLY: how many blocks THAT TRADED are grouped into one bar — the N in `tf=Nb`, i.e. the width ASKED FOR. ⛔ This is not a chain span: the bar runs from `first_block` to `last_block` and that distance is always at least this number and usually far greater. Compare `trading_blocks` for what the bar actually HOLDS. Null on the time basis, and it must stay null there — a duration does not hold a fixed number of blocks, so filling this in from an average block time would publish an equivalence that does not hold."},"trading_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Trading Blocks","description":"TRADING-BLOCK BASIS ONLY: how many blocks with a priced fill this bar ACTUALLY holds. Equals `bucket_blocks` on every bar except possibly the NEWEST, which holds the REMAINDER while the chain fills it. ⚠️ IT IS NOT ALWAYS SHORT: when the pair's total count of trading blocks divides evenly by N the newest bar is full too, and at `tf=1b` (N=1) every bar including the newest holds exactly 1, so none is ever partial. Do not read a partial newest bar as guaranteed — compare this field against `bucket_blocks`, which is the only reliable test and the reason this field exists: without it a forming bar's volume looks like a collapse rather than a bar in progress. Null on the time basis."}},"type":"object","required":["t","o","h","l","c","v_base","v_quote","n","first_block","last_block"],"title":"CandleRow","description":"One bar from `GET /api/candles`. o/h/l/c are never null: bars are built\nonly from fills that carry a price.\n\n⭐ ONE ROW CLASS, TWO BUCKETING BASES, AND THE KEY SET IS THE SAME ON BOTH.\n`/api/candles` buckets either by TIME (`tf=5m…1w`) or by TRADING-BLOCK COUNT\n(`tf=1b…1008b`); the envelope's `bucket_basis` says which. A block bar fills\n`bucket_block`/`bucket_blocks`/`trading_blocks` and a time bar publishes all\nthree as NULL — absent BY BASIS, never missing data — so no consumer has to\ntest for a key's existence.\n\n⛔ THE BLOCK BASIS WAS REDEFINED IN PLACE. `Nb` used to group N CONSECUTIVE\nCHAIN BLOCKS; it now groups N blocks THAT CARRIED A PRICED FILL. `bucket_basis`\nmoved from `block` to `trading_block` in the same change precisely so a consumer\nholding the old meaning finds out."},"CandlesEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/CandleRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"pair":{"type":"string","title":"Pair"},"venue":{"type":"string","title":"Venue"},"tf":{"type":"string","title":"Tf"},"bucket_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bucket Basis","description":"Which bucketing produced these bars: `time` (tf 5m…1w, buckets are DURATIONS) or `trading_block` (tf 1b…1008b, buckets are COUNTS OF BLOCKS THAT TRADED). ⛔ This value used to read `block`, when `Nb` meant N consecutive CHAIN blocks; the word changed with the meaning so that a consumer holding the old definition can detect it rather than silently misread every bar. ⛔ The two bases are not interchangeable on this chain and neither is derivable from the other, which is why this is published rather than inferred from `tf`. Declared optional in this schema; the handler emits it on every branch."},"bucket_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bucket Blocks","description":"The bar width in TRADING BLOCKS on the trading-block basis; null on the time basis. Same field name and meaning as `CandleRow.bucket_blocks`, so a bar detached from this envelope still carries its own width."},"price_basis":{"type":"string","title":"Price Basis"},"truncated":{"type":"boolean","title":"Truncated"},"absence_means":{"type":"string","title":"Absence Means"},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","count","pair","venue","tf","price_basis","truncated","absence_means","source"],"title":"CandlesEnvelope","description":"GET /api/candles.\n\n`absence_means` is always present; only its text varies by branch."},"Conformance":{"type":"string","enum":["conformant","nonconformant","unknown"],"title":"Conformance","description":"★ THREE STATES, AND THE THIRD IS THE FINDING. Some launches carry a NULL\n`params_conformant` because the ANNOUNCEMENT ITSELF was invalid and carries no\nparameters to judge; folding them into `nonconformant` would over-report\nnon-conformance, and that is exactly the boolean/string disagreement this\ncontract exists to prevent.\n⚠️ THE STRUCTURE IS THE CLAIM; THE COUNT IS CHAIN STATE. AS OF BLOCK 963,510\n(2026-08-22) the split was 94 conformant / 298 nonconformant / 17 unknown of\n409. It was 88 / 298 / 17 of 403 twelve hours earlier — six launches confirmed\nin between. **Read `summary`, which is dated on every response.**"},"ConformanceEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/LaunchConformanceRow"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"summary":{"$ref":"#/components/schemas/ConformanceSummary"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","summary"],"title":"ConformanceEnvelope"},"ConformanceSummary":{"properties":{"conformant":{"$ref":"#/components/schemas/Measured_int_"},"nonconformant":{"$ref":"#/components/schemas/Measured_int_"},"unknown":{"$ref":"#/components/schemas/Measured_int_","description":"Announcement itself invalid; no parameters to judge. NOT 'no'."},"start_before_announce":{"$ref":"#/components/schemas/Measured_int_"},"total_launches":{"$ref":"#/components/schemas/Measured_int_"}},"additionalProperties":false,"type":"object","required":["conformant","nonconformant","unknown","start_before_announce","total_launches"],"title":"ConformanceSummary"},"Coverage":{"properties":{"source":{"type":"string","title":"Source","description":"The view or table actually read."},"filters_applied":{"items":{"type":"string"},"type":"array","title":"Filters Applied","description":"Every filter that shaped this answer, in words."},"block_floor":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Block Floor","description":"Below this block the family does not exist in this archive."},"block_range_returned":{"anyOf":[{"items":{"type":"integer"},"type":"array"},{"type":"null"}],"title":"Block Range Returned","description":"[min, max] block of the rows returned. null means EITHER no rows were returned OR this route's rows are not block-keyed (the venue-gap series is keyed by sample time; the failure taxonomy is keyed by reason). ⚠️ Read row_count to tell those apart."},"rows_newer_than_as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rows Newer Than As Of Block","description":"How many returned rows carry a block ABOVE freshness.as_of_block. ⚠️ Normally 0. It can be non-zero because freshness is read before the page, on a separate connection, so blocks ingested in between arrive under an older stamp. Counted, not promised: an agent pinning as_of_block for reproducibility can see exactly how many rows it would not get back. null = this route's rows are not block-keyed."},"source_populated":{"type":"boolean","title":"Source Populated","description":"Whether the SOURCE holds any rows at all. Checked, never assumed."},"source_checked_at":{"type":"string","format":"date-time","title":"Source Checked At","description":"When source_populated was established."},"excluded":{"items":{"type":"string"},"type":"array","title":"Excluded","description":"What this answer does NOT cover. An empty list is a positive claim."}},"additionalProperties":false,"type":"object","required":["source","filters_applied","block_floor","source_populated","source_checked_at","excluded"],"title":"Coverage","description":"What this answer covers, and — as a required field — what it does not."},"CoverageBlockEnvelope":{"properties":{"height":{"type":"integer","title":"Height"},"coverage":{"type":"string","title":"Coverage"},"reason":{"type":"string","title":"Reason"},"poller_row":{"type":"boolean","title":"Poller Row"},"poller_complete":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Poller Complete"},"event_count_claimed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Event Count Claimed"},"events_held":{"type":"integer","title":"Events Held"},"types_covered":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Types Covered"},"types_covered_basis":{"type":"string","title":"Types Covered Basis"},"types_on_chain":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Types On Chain"},"missing_types":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Missing Types"},"source":{"type":"string","title":"Source"},"note":{"type":"string","title":"Note"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["height","coverage","reason","poller_row","poller_complete","event_count_claimed","events_held","types_covered","types_covered_basis","types_on_chain","missing_types","source","note"],"title":"CoverageBlockEnvelope","description":"GET /api/coverage/block/{height}.\n\n`poller_complete` and `event_count_claimed` are null when no `blocks` row exists;\n`types_covered`, `types_on_chain` and `missing_types` are null when no census is\nrecorded — and for `missing_types`, null is semantically distinct from []."},"DeadlineRewriteRow":{"properties":{"asset":{"type":"string","title":"Asset"},"tx_hash":{"type":"string","title":"Tx Hash"},"announce_block":{"type":"integer","title":"Announce Block"},"original_deadline_block":{"type":"integer","title":"Original Deadline Block"},"rewritten_deadline_block":{"type":"integer","title":"Rewritten Deadline Block"},"deadline_delta_blocks":{"type":"integer","title":"Deadline Delta Blocks"},"moved_earlier":{"type":"boolean","title":"Moved Earlier"},"rewrite_block":{"type":"integer","title":"Rewrite Block"},"rewrite_event_index":{"type":"integer","title":"Rewrite Event Index"},"rewritten_to_own_block":{"type":"boolean","title":"Rewritten To Own Block"}},"type":"object","required":["asset","tx_hash","announce_block","original_deadline_block","rewritten_deadline_block","deadline_delta_blocks","moved_earlier","rewrite_block","rewrite_event_index","rewritten_to_own_block"],"title":"DeadlineRewriteRow"},"DeadlineRewritesEnvelope":{"properties":{"rewrites":{"items":{"$ref":"#/components/schemas/DeadlineRewriteRow"},"type":"array","title":"Rewrites"},"rewrites_observed":{"type":"integer","title":"Rewrites Observed"},"limit":{"type":"integer","title":"Limit"},"truncated":{"type":"boolean","title":"Truncated"},"availability":{"$ref":"#/components/schemas/RewriteAvailability"},"absence_means":{"type":"string","title":"Absence Means"},"direction_note":{"type":"string","title":"Direction Note"},"population_note":{"type":"string","title":"Population Note"},"original_note":{"type":"string","title":"Original Note"},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["rewrites","rewrites_observed","limit","truncated","availability","absence_means","direction_note","population_note","original_note","source"],"title":"DeadlineRewritesEnvelope","description":"GET /api/launches/deadline-rewrites."},"DispenseRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"dispenser_tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dispenser Tx Hash"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"The dispenser address."},"destination":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Destination","description":"The buyer."},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"dispense_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Dispense Quantity Normalized"},"btc_amount_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Btc Amount Normalized"},"dispensed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Dispensed At"}},"additionalProperties":false,"type":"object","required":["block_index","event_index","tx_hash","dispenser_tx_hash","source","destination","asset","dispense_quantity_normalized","btc_amount_normalized","dispensed_at"],"title":"DispenseRow"},"DispenserOpen":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin"},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"give_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Quantity Normalized"},"give_remaining_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining Normalized","description":"⚠️ AT OPEN. Refills are NOT in this archive — see coverage.excluded."},"escrow_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Escrow Quantity Normalized"},"satoshirate":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Satoshirate","description":"Satoshis per give_quantity. The dispenser's price."},"satoshirate_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Satoshirate Normalized"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"oracle_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oracle Address"},"opened_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Opened At"}},"additionalProperties":false,"type":"object","required":["block_index","event_index","tx_hash","source","origin","asset","give_quantity_normalized","give_remaining_normalized","escrow_quantity_normalized","satoshirate","satoshirate_normalized","status","oracle_address","opened_at"],"title":"DispenserOpen"},"DispenserOpenCensus":{"properties":{"confirmed_open":{"$ref":"#/components/schemas/Measured_int_","description":"Latest DISPENSER_UPDATE says open, with inventory left."},"presumed_open":{"$ref":"#/components/schemas/Measured_int_","description":"No DISPENSER_UPDATE has EVER been observed for this dispenser. Not a claim that it is open — a statement that we never saw it close."},"total_open":{"$ref":"#/components/schemas/Measured_int_","description":"confirmed_open + presumed_open. ⛔ Do not quote this alone."}},"additionalProperties":false,"type":"object","required":["confirmed_open","presumed_open","total_open"],"title":"DispenserOpenCensus","description":"The confirmed/presumed split, computed over THIS RESPONSE'S FILTERS.\n\n⚠️ THE DENOMINATOR IS THE HALF THAT ROTS, so it is not a constant here: these\nthree numbers are re-measured per request under the same `asset` / block-range\npredicates as `rows`, never over the whole view. A census under a different\nfilter than the page it describes is a number that reads as a fact and is not\none. ⛔ `total_open` is the sum, and it is the number NOBODY should quote on\nits own — publishing it beside its two components is the whole point."},"DispenserOpenNow":{"properties":{"opened_block":{"type":"integer","title":"Opened Block"},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"The dispenser address — where the tokens are."},"origin":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Origin"},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"asset_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset Divisible","description":"READ from the opening event, never assumed. NULL means unknown, which is NOT 'divisible'."},"give_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Quantity Normalized","description":"Tokens released per payment of `satoshirate`. A dispenser term, not a balance."},"escrow_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Escrow Quantity Normalized","description":"AT OPEN. History."},"give_remaining_at_open_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining At Open Normalized","description":"⚠️ AT OPEN — the same number `/api/dispensers` serves under the bare name `give_remaining_normalized`. Kept here so a row can show both."},"satoshirate":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Satoshirate","description":"Satoshis per give_quantity. The dispenser's price."},"satoshirate_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Satoshirate Normalized"},"oracle_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Oracle Address"},"opened_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Opened At"},"give_remaining":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining","description":"NOW, raw integer."},"give_remaining_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining Normalized","description":"⭐ NOW, in the asset's own units — the field this route exists for. Read from the node's own `give_remaining_normalized` on the latest DISPENSER_UPDATE that carried one; NEVER divided by 1e8 here. ⛔⛔ ON A `presumed_open` ROW THIS IS NOT A FRESH READING. With no update ever observed there is nothing to read, so it falls back to the opening event and equals `give_remaining_at_open_normalized` exactly. Check `open_evidence` before treating this as current. ⚠️ Does NOT account for refills: REFILL_DISPENSER is not held."},"status_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status Code","description":"The chain's raw code. '0' open, '10' closed, '11' closing."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Our reading of status_code. An unrecognised code becomes 'unknown_status_<code>' and is excluded from this route — a code we cannot read is not a dispenser we may tell you to buy from."},"update_observed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Update Observed","description":"⛔ false does NOT mean 'closed'. It means this archive has never seen this dispenser say anything since it opened."},"updated_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Updated Block","description":"Block of the latest DISPENSER_UPDATE, or null if none."},"open_evidence":{"type":"string","title":"Open Evidence","description":"⛔⛔ 'confirmed_open' — the chain told us it is open and we hold the update. 'presumed_open' — we watched it open and never observed it close; NOT KNOWN TO BE OPEN. The archive is a live-observation window that began at a block, so a dispenser that opened and closed before we were watching its updates is indistinguishable from one still standing."}},"additionalProperties":false,"type":"object","required":["opened_block","event_index","tx_hash","source","origin","asset","asset_divisible","give_quantity_normalized","escrow_quantity_normalized","give_remaining_at_open_normalized","satoshirate","satoshirate_normalized","oracle_address","opened_at","give_remaining","give_remaining_normalized","status_code","status","update_observed","updated_block","open_evidence"],"title":"DispenserOpenNow","description":"One dispenser this archive believes is STANDING RIGHT NOW, with what is\nleft in it — the question `/api/dispensers` cannot answer.\n\n⛔⛔ READ `open_evidence` BEFORE ANY OTHER FIELD. Two thirds of these rows are\n`presumed_open`: we watched them open and have never observed them close. That\nis NOT the same claim as `confirmed_open`, and a consumer that treats them as\none is buying a number this archive cannot support. See the field's own\ndescription."},"DispenserOpenNowEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/DispenserOpenNow"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/DispenserOpenCensus"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"DispenserOpenNowEnvelope"},"Envelope_DispenseRow_":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/DispenseRow"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats"],"title":"Envelope[DispenseRow]"},"Envelope_DispenserOpen_":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/DispenserOpen"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats"],"title":"Envelope[DispenserOpen]"},"EventCountRow":{"properties":{"event":{"type":"string","title":"Event"},"count":{"type":"integer","title":"Count"}},"type":"object","required":["event","count"],"title":"EventCountRow"},"EventCountsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/EventCountRow"},"type":"array","title":"Result"},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when this answer's cached rows are OLDER THAN THE ROUTE'S CACHE TTL — the rebuild did not finish and you were served the LAST GOOD answer instead of a 503. The 200 is then a labelled fallback, not a fresh answer. ⚠️ THE PREDICATE IS THE ROWS' AGE AGAINST THE TTL, not 'the fallback path ran': a concurrent rebuild can finish while this one fails, and that answer is genuinely fresh and is reported so — read `rebuild_failing` for the other axis. ⛔ Not `freshness.stale`, which is ARCHIVE-vs-CHAIN."},"rows_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rows Age S","description":"How old the cached part of this answer is, in seconds, since the build that produced it FINISHED. A DURATION from a monotonic clock, never a wall-clock timestamp, so an ntp step cannot move it. Recovered from the cache's own stamp even when another request did the build."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route's cache is REMEMBERED right now — the route is inside a labelled-fallback window and declining to re-attempt for a cooldown. ⛔ A THIRD AXIS, NOT A SECOND NAME: the combination it exists for is FRESH + FAILING (a concurrent flight stored rows at this instant while this request's own build was cancelled). ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'."},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result"],"title":"EventCountsEnvelope","description":"GET /api/events/counts.\n\n`stale_fallback`, `rows_age_s` and `rebuild_failing` describe the cached counts.\n`as_of_block` on this route is the head the counts were BUILT at, so it needs no\n`built_at_block` twin."},"EventRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"event":{"type":"string","title":"Event"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"params":{"additionalProperties":true,"type":"object","title":"Params","description":"★ DELIBERATELY UNTYPED, and this is the honest answer rather than a gap. `params` is the node's raw JSONB payload and its keys differ per `event` type across 62 event types — a NEW_FAIRMINT and a BLOCK_PARSED share no field. Declaring any one shape here would be false for 61 of them. ⛔ `BLOCK_PARSED.params.messages_hash` IS NODE-LOCAL AND TIME-VARYING — it is NOT a consensus value and it is NOT comparable across nodes. A mismatch on this one key against another node is NOT evidence that this archive is wrong; it is a property of the field. Compared over 22 strided heights (280,000-963,400, 364 event rows, field by field, two independent operators): the event SETS agree everywhere, and so do the two CONSENSUS hashes `ledger_hash` and `txlist_hash` — `messages_hash` is the ONLY key both nodes emit and disagree on. In a whole-population count over blocks 952,800-963,459, 7,407 of those 10,660 BLOCK_PARSED rows held a value NO rostered node served, not even the same build that produced it. ⇒ Do not diff it, do not reconcile against it, and do not derive from it: nothing in this API reads it. ⚠️ AND THIS ROW CANNOT TELL YOU WHICH BUILD PRODUCED THE VALUE. Provenance is RECORDED PER ROW IN THE ARCHIVE — `events.source_node`, `events.node_version`, `events.node_commit` name the node and the build that wrote a row, where they were recorded (rows ingested before these columns existed carry none). Which build wrote a row is recorded, but it is NOT PUBLISHED HERE: `node_version` / `node_commit` are published by no route at all, so nothing in this response attributes this value to a build. The only provenance published anywhere in this API is `MempoolEnvelope.source_node`, `OrphanEvent.source_node`. On a row of the orphaned-event log, the `source_node` beside this payload is that row's own, copied from the archive when the event was orphaned (null where none was recorded), and it names the NODE, never the build; an archived event row outside that log carries no provenance field at all, and the mempool's `source_node` describes mempool sightings, not archived events. Publishing the build per row would be a response-shape change, which this API has declined, so the honest answer is that the build attribution exists in the archive and you cannot read it from here. ⇒ That is the reason to disregard a mismatch on this key rather than to reconcile it."}},"type":"object","required":["block_index","event_index","event","tx_hash","params"],"title":"EventRow","description":"One archived Counterparty event."},"EventsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/EventRow"},"type":"array","title":"Result"},"next_cursor":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Cursor"},"truncated":{"type":"boolean","title":"Truncated"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"tx_hash_filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash Filter"},"tx_hash_filter_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash Filter Note"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","next_cursor","truncated","note","tx_hash_filter","tx_hash_filter_note"],"title":"EventsEnvelope","description":"GET /api/events.\n\nAll six keys are always present; the tx_hash fields are published as null rather\nthan omitted, on purpose."},"FailedMintAttempt":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"The address that attempted the mint and failed."},"reason":{"type":"string","title":"Reason","description":"The node's own `status` string, verbatim."},"tx_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Tx Index"},"attempted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Attempted At"}},"additionalProperties":false,"type":"object","required":["block_index","event_index","tx_hash","source","reason","tx_index","attempted_at"],"title":"FailedMintAttempt","description":"One `NEW_FAIRMINT` the node recorded as invalid: the payer, the block, and\nthe reason. ⚠️ THESE ROWS CARRY NO ASSET AND NO PAID QUANTITY — the node emits\nthem without one — so they can NOT be attributed to a launch. That is a\nproperty of the source, not an omission here."},"FailedMintCensus":{"properties":{"new_fairmint_events_held":{"$ref":"#/components/schemas/Measured_int_"},"valid_mints":{"$ref":"#/components/schemas/Measured_int_","description":"What v_fairmint and /api/mints serve."},"failed_attempts":{"$ref":"#/components/schemas/Measured_int_","description":"What v_fairmint drops — and this route serves. AS OF BLOCK 963,510 (2026-08-22) this was 22,799 of 213,728, or 10.667 percent; both counts come back dated on every response, so use those, not this sentence."},"failed_share_pct":{"$ref":"#/components/schemas/Measured_Decimal_"},"distinct_reasons":{"$ref":"#/components/schemas/Measured_int_"}},"additionalProperties":false,"type":"object","required":["new_fairmint_events_held","valid_mints","failed_attempts","failed_share_pct","distinct_reasons"],"title":"FailedMintCensus","description":"★★ THE FOOTGUN, PUBLISHED. Anyone computing a mint rate from `v_fairmint`\n(or from `/api/mints`) while comparing to a node's raw `NEW_FAIRMINT` count is\nlow by `failed_share_pct` and will not know."},"FailedMintEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/FailedMintAttempt"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/FailedMintCensus"},"built_at_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Built At Block","description":"The archive head the rows and the census were COUNTED at. Equals `freshness.as_of_block` on a fresh answer; on a `stale_fallback` answer it is the older head the last good pass ran against, and every census number's own `as_of_block` says the same."},"stale_fallback":{"type":"boolean","title":"Stale Fallback","description":"True when this answer was NOT counted at the current archive head: the per-head rebuild failed and you were served the LAST GOOD pass (`built_at_block`). The 200 is then a labelled fallback. ⚠️ A HEAD COMPARISON, like `/api/book`'s — never 'the fallback path ran'.","default":false},"rebuild_failing":{"type":"boolean","title":"Rebuild Failing","description":"True when a failed rebuild of this route's cached pass is REMEMBERED right now (a cooldown is in force). A third axis: it can be true beside `stale_fallback: false` when a concurrent rebuild succeeded while this request's failed. A process restart clears it with no transition.","default":false}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"FailedMintEnvelope","description":"⭐ The DEFAULT request (every parameter at its default) is served\nfrom a per-archive-head cache, and these three fields say how it was served —\nthe same axes and names `/api/markets/ranking` and `/api/book` publish for their\nper-head caches. Always present; on every non-default request (which is never\ncached) they read `built_at_block == freshness.as_of_block`, `false`, `false`.\n⛔ `stale_fallback` is CACHE-vs-ARCHIVE and is NOT `freshness.stale`\n(ARCHIVE-vs-CHAIN): both appear on this response and can disagree without either\nbeing wrong."},"FailedMintReason":{"properties":{"reason":{"type":"string","title":"Reason"},"attempts":{"type":"integer","title":"Attempts"},"first_block":{"type":"integer","title":"First Block"},"last_block":{"type":"integer","title":"Last Block"},"distinct_payers":{"type":"integer","title":"Distinct Payers"},"measured_at":{"type":"string","format":"date-time","title":"Measured At","description":"These are aggregates. This is when they were computed."}},"additionalProperties":false,"type":"object","required":["reason","attempts","first_block","last_block","distinct_payers","measured_at"],"title":"FailedMintReason"},"FailedMintReasonEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/FailedMintReason"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/FailedMintCensus"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"FailedMintReasonEnvelope"},"ForkRow":{"properties":{"first_orphaned_at":{"type":"string","title":"First Orphaned At"},"from_block":{"type":"integer","title":"From Block"},"to_block":{"type":"integer","title":"To Block"},"blocks_removed":{"type":"integer","title":"Blocks Removed"},"blocks_replaced":{"type":"integer","title":"Blocks Replaced"},"blocks_no_canonical":{"type":"integer","title":"Blocks No Canonical"},"blocks_returned_identical":{"type":"integer","title":"Blocks Returned Identical"},"distinct_block_hashes":{"type":"integer","title":"Distinct Block Hashes"},"events_removed":{"type":"integer","title":"Events Removed"},"events_truly_orphaned":{"type":"integer","title":"Events Truly Orphaned"},"first_block_time":{"type":"string","title":"First Block Time"},"last_block_time":{"type":"string","title":"Last Block Time"}},"type":"object","required":["first_orphaned_at","from_block","to_block","blocks_removed","blocks_replaced","blocks_no_canonical","blocks_returned_identical","distinct_block_hashes","events_removed","events_truly_orphaned","first_block_time","last_block_time"],"title":"ForkRow"},"Freshness":{"properties":{"answered_at":{"type":"string","format":"date-time","title":"Answered At","description":"When this response was built."},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"Highest COMPLETE block in the archive. null = could not read it."},"as_of_block_time":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"As Of Block Time","description":"Bitcoin timestamp of as_of_block, or null if unmapped."},"chain_tip":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Chain Tip","description":"The tip the poller last saw upstream."},"lag_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lag Blocks","description":"chain_tip - as_of_block. null when either is unknown."},"poll_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Poll Age S","description":"Seconds since the poller last checked in."},"poller_alive":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Poller Alive","description":"poll_age_s <= max_poll_age_s. null = could not determine."},"ingest_error_present":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ingest Error Present","description":"Whether ingest_health carries an error. Text NOT republished."},"head_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Head Age S","description":"answered_at - as_of_block_time, in seconds. ★ THE ONE STALENESS SIGNAL THE POLLER DOES NOT WRITE: lag is chain_tip minus as_of_block and BOTH are written by the poller, so they freeze together when it dies. Negative means the head block is timestamped in the future."},"max_poll_age_s":{"type":"number","title":"Max Poll Age S","description":"The threshold poller_alive was judged against."},"max_lag_blocks":{"type":"integer","title":"Max Lag Blocks","description":"The threshold lag was judged against."},"max_head_age_s":{"type":"number","title":"Max Head Age S","description":"The threshold head_age_s was judged against. Derived as max_lag_blocks x 600s, so there is ONE operator knob, not three."},"stale":{"type":"boolean","title":"Stale","description":"TRUE if this answer must not be trusted as current."},"stale_reasons":{"items":{"type":"string"},"type":"array","title":"Stale Reasons","description":"Why, in prose. Empty iff stale is false."},"stale_reason_codes":{"items":{"type":"string"},"type":"array","title":"Stale Reason Codes","description":"The same reasons as STABLE MACHINE CODES — branch on these, not on the prose. Four of them (poller_stalled, lag_unknown, lag_exceeded, ingest_error_recorded) are spelled exactly as /api/health spells them, so the free and paid surfaces can be diffed by a machine."}},"additionalProperties":false,"type":"object","required":["answered_at","as_of_block","as_of_block_time","chain_tip","lag_blocks","poll_age_s","poller_alive","ingest_error_present","head_age_s","max_poll_age_s","max_lag_blocks","max_head_age_s","stale","stale_reasons","stale_reason_codes"],"title":"Freshness","description":"The archive's own state at the moment this response was built.\n\n⚠️ THE ONE FIELD A MACHINE SHOULD BRANCH ON IS `stale`. Everything else is the\nevidence for it. `stale_reasons` is empty if and only if `stale` is false."},"GapsEnvelope":{"properties":{"from":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"From"},"to":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"To"},"scanned_blocks":{"type":"integer","title":"Scanned Blocks"},"limit":{"type":"integer","title":"Limit"},"gaps":{"items":{"type":"integer"},"type":"array","title":"Gaps"},"truncated":{"type":"boolean","title":"Truncated"},"note":{"type":"string","title":"Note"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["from","to","scanned_blocks","limit","gaps","truncated","note"],"title":"GapsEnvelope","description":"GET /api/gaps. Every branch returns the same key set.\n\n`from` and `to` are null only when the archive holds no COMPLETE block — an\narchive holding only incomplete blocks answers the same way — and `note` then\nsays so. `gaps` is a FLAT LIST OF HEIGHTS, not row objects."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthEnvelope":{"properties":{"ok":{"type":"boolean","title":"Ok"},"ok_reasons":{"items":{"type":"string"},"type":"array","title":"Ok Reasons"},"disk_free_gb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disk Free Gb"},"disk_total_gb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Disk Total Gb"},"disk_ok":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Disk Ok"},"disk_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Disk Error"},"min_free_gb":{"type":"number","title":"Min Free Gb"},"mem_total_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mem Total Mb","description":"MemTotal, MB. NULL when memory could not be measured at all — see `mem_error`."},"mem_available_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Mem Available Mb","description":"MemAvailable, MB — the number `mem_ok` is judged on. NULL when unmeasured. ⚠️ NOT MemFree, which exists on kernels before 3.14 and is a different, much smaller number; a partial read is reported as an error rather than substituted for."},"swap_total_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Swap Total Mb","description":"SwapTotal, MB. NULL when unmeasured."},"swap_used_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Swap Used Mb","description":"SwapTotal - SwapFree, MB. NULL unless BOTH were read."},"mem_ok":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mem Ok","description":"⛔ TRI-STATE, exactly like `disk_ok`, and the third state is the point: NULL means memory could not be measured on this host (`/proc/meminfo` does not exist off Linux) and that is NOT A PASS — a check that passes when it cannot verify is worse than no check, because it is believed. false = measured and below `min_avail_mb`. true = measured and at or above it."},"mem_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mem Error","description":"Names WHY the probe could not produce a number — the exception for an unreadable /proc/meminfo, or the specific lines that were missing from it. NULL when the probe succeeded."},"min_avail_mb":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Min Avail Mb","description":"The threshold `mem_ok` was judged against, published so the judgement is auditable from the payload instead of from the source."},"mem_gates_ok":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mem Gates Ok","description":"⛔ THE DECISION, ON THE WIRE. false — memory contributes to NEITHER `ok` NOR `ok_reasons`, deliberately. A consumer seeing `mem_ok: false` beside `ok: true` is NOT looking at a contradiction and must not have to read the source to learn that: memory is reported here, and this route's status code does not answer for it. If memory is ever made to gate, this field flips with it and says so."},"last_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Block"},"chain_tip":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Chain Tip"},"lag_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Lag Blocks"},"poller_alive":{"type":"boolean","title":"Poller Alive"},"poll_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Poll Age S"},"last_poll_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Poll At"},"last_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error"},"last_error_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Error At"},"last_error_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last Error Age S"},"build_sha":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Sha","description":"Git sha of the tree the server deploy shipped, written to the build-identity file at deploy time and read ONCE at process start. ⛔ null means the identity could NOT be read and `build_id_error` says why — it NEVER means 'unchanged' and is never guessed at. There is no git checkout on the server, so this file is the only thing on the machine that knows."},"build_committed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Committed At","description":"COMMITTER date of `build_sha` (git %cI), ISO-8601. Distinguishes 'deployed an hour ago' from 'deployed an hour ago from a three-week-old commit'. Committer date is the deliberate choice — author date survives rebases and cherry-picks unchanged, so it CAN in principle predate the deployed content — but that is a property of git, not a measurement of any particular commit."},"build_branch":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Branch","description":"Branch the deploy ran from, normally `main`. ⛔ null is AMBIGUOUS: a detached HEAD publishes null, and the identity reader also returns null for the FILE-DERIVED keys when the build-identity file is absent, malformed or unreadable — the state a server is in before its first identity-carrying deploy. ⚠️ NOT every key: `build_id_read_at` and `build_id_error` are still populated, which is what makes the discriminator below work at all. ⇒ READ `build_id_error` TO TELL THEM APART — null with a null error is a detached HEAD; null with an error is an identity that could not be read. Publishing the literal \"HEAD\" (what `rev-parse --abbrev-ref` returns when detached) would be worse than either."},"build_dirty":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Build Dirty","description":"⚠️ true = the deploying tree had UNCOMMITTED changes, so `build_sha` does not fully describe what shipped. The deploy is allowed to be dirty; the reader is not allowed to be misled about it."},"build_deployed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Deployed At","description":"When the deploy wrote the build-identity file, ISO-8601 UTC. ⛔ THIS IS THE DEPLOYING MACHINE'S CLOCK, and `build_id_read_at` is the BOX's — they cross two machines and are NOT comparable to the second. For 'did the running process actually re-read this?' use `build_restart_pending`, which is a same-machine mtime comparison and answers that question directly."},"build_commits_server":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Build Commits Server","description":"★ THE API's VERSION NUMBER, and the one the terminal page renders as `api`. Commits reachable from `build_sha` that touched the server's code (`git rev-list --count <sha> -- server`), computed at deploy time from THAT sha and not from HEAD. ⛔ IT DESCRIBES THE RUNNING PROCESS, NOT THE DEPLOYED PAYLOAD — this module is read once at import, so when `build_restart_pending` is true a NEWER build is on disk and this number is still the correct one for the code answering you. Read the two fields together; calling this 'the deployed version' is the exact lie the pair exists to prevent. ⛔ NOT COMPARABLE TO THE PAGE's `site` COUNTER and nothing may subtract them: they are overlapping path-filtered subsets of ONE history (some commits touch both the site and the server), not two halves of a total. Each answers 'did my deploy land' for its own surface only. ⚠️ NOT AN ORDERING AND NOT SEMVER: a reachable-commit count is monotonic along one line of deploys but two branches can produce the same number. ⛔ NO FIGURE IS QUOTED IN THIS DESCRIPTION — it moves with every server commit, so any number written here would be stale before the next deploy. null = this server's build-identity file predates the field, or carried a value that was not a non-negative int (a bool included); absence never becomes a guess."},"build_id_read_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Id Read At","description":"When THIS PROCESS read the file, by the BOX's clock. ⚠️ Comparing it to `build_deployed_at` crosses two machines' clocks and is only a coarse signal. `build_restart_pending` is the exact answer to the same question and is measured entirely on the box."},"build_restart_pending":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Build Restart Pending","description":"⛔ THE HONEST FIELD. true = the build-identity file on disk has moved past what this process read — code was deployed and the service was NOT restarted, so every other route is served by a build this identity does not describe. ★ TRI-STATE like `disk_ok`: null means undeterminable and is explicitly NOT false. Without this field a skipped restart would make /api/health announce a fresh sha for stale code."},"build_id_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Build Id Error","description":"null when the identity was read cleanly; otherwise the reason `build_sha` is null (file absent, unparseable, or missing a required key). Absence always carries a reason here."},"max_lag_blocks":{"type":"integer","title":"Max Lag Blocks"},"max_poll_age_s":{"type":"number","title":"Max Poll Age S"}},"type":"object","required":["ok","ok_reasons","disk_free_gb","disk_total_gb","disk_ok","disk_error","min_free_gb","last_block","chain_tip","lag_blocks","poller_alive","poll_age_s","last_poll_at","last_error","last_error_at","last_error_age_s","max_lag_blocks","max_poll_age_s"],"title":"HealthEnvelope","description":"GET /api/health.\n\n⚠️ Served with status 200 OR 503 and THE SAME BODY SHAPE when the archive is\nhealthy or unhealthy — only the code differs. ⛔ When the DATABASE is down the\n503 body is `{\"detail\": \"...\"}` — an `ErrorDetail`, not this envelope. So\n/api/health is the ONE operation that can serve TWO DIFFERENT 503 SHAPES, and\nits declaration says so (`oneOf`, not one model). A consumer that assumes \"same\nbody shape\" and reads `body[\"ok\"]` gets a KeyError on exactly the failure it was\nwritten to detect.\n★ `disk_ok` is TRI-STATE on purpose: null means unmeasured and is explicitly NOT\nfalse. `last_error_at` can be non-null while `last_error` is null, and\n`last_error_age_s` is then deliberately suppressed."},"LaunchConformanceRow":{"properties":{"tx_hash":{"type":"string","title":"Tx Hash"},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"creator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator"},"block_index":{"type":"integer","title":"Block Index"},"announced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Announced At"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"conformance":{"$ref":"#/components/schemas/Conformance"},"params_conformant":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Params Conformant"},"start_after_announce":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Start After Announce","description":"A mint window cannot open before its own launch tx confirmed. False is a real finding."},"mint_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mint Count","description":"null unless include_mint_rollup=true."},"minters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minters"},"xcp_raised":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Xcp Raised"}},"additionalProperties":false,"type":"object","required":["tx_hash","asset","creator","block_index","announced_at","status","conformance","params_conformant","start_after_announce","mint_count","minters","xcp_raised"],"title":"LaunchConformanceRow"},"LaunchConstants":{"properties":{"soft_cap_xcp":{"type":"number","title":"Soft Cap Xcp"},"min_participants":{"type":"integer","title":"Min Participants"},"mint_window_blocks":{"type":"integer","title":"Mint Window Blocks"},"max_per_addr_xcp":{"type":"number","title":"Max Per Addr Xcp"}},"type":"object","required":["soft_cap_xcp","min_participants","mint_window_blocks","max_per_addr_xcp"],"title":"LaunchConstants"},"LaunchHeader":{"properties":{"tx_hash":{"type":"string","title":"Tx Hash"},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"creator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator"},"block_index":{"type":"integer","title":"Block Index"},"announced_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Announced At"},"start_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start Block"},"deadline_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Deadline Block"},"hard_cap":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Hard Cap"},"price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Price"},"params_conformant":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Params Conformant","description":"null means UNKNOWN, never 'no'. See /api/launches/conformance."},"start_after_announce":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Start After Announce"}},"additionalProperties":false,"type":"object","required":["tx_hash","asset","creator","block_index","announced_at","start_block","deadline_block","hard_cap","price","params_conformant","start_after_announce"],"title":"LaunchHeader"},"LaunchMintersBasis":{"properties":{"minters":{"type":"string","title":"Minters"},"minters_distinct":{"type":"string","title":"Minters Distinct"}},"type":"object","required":["minters","minters_distinct"],"title":"LaunchMintersBasis","description":"Which population each `totals` minter figure counts, as data."},"LaunchRow":{"properties":{"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"status":{"type":"string","title":"Status"},"creator":{"type":"string","title":"Creator"},"tx":{"type":"string","title":"Tx"},"pct":{"type":"number","title":"Pct"},"xcp":{"type":"number","title":"Xcp"},"minters":{"type":"integer","title":"Minters"},"mints":{"type":"integer","title":"Mints"},"start":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Start"},"deadline":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Deadline"},"to_start":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"To Start"},"to_deadline":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"To Deadline"},"elapsed":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Elapsed"},"pace":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Pace"}},"type":"object","required":["asset","status","creator","tx","pct","xcp","minters","mints","start","deadline","to_start","to_deadline","elapsed","pace"],"title":"LaunchRow","description":"One fair-mint launch, as it appears in all five lists of /api/launches.\n\n★ `asset` IS NULLABLE and that is the finding, not an oversight: the\n`unknown_conformance` bucket holds launches whose ANNOUNCEMENT was itself\nrejected, so the archive has no asset name for them — and with it, no start,\ndeadline or pace either.\n`pace`/`elapsed` are null for every non-open launch."},"LaunchStatus":{"properties":{"tx_hash":{"type":"string","title":"Tx Hash","description":"The launch's FAIRMINTER announcement tx_hash."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"The newest status. ⚠️ null is REAL and means the newest update carried no status key at all — a deadline rewrite. That is 'un-statused', which is a different fact from 'un-updated', and it is not filtered out."},"status_block":{"type":"integer","title":"Status Block","description":"The block that newest update confirmed in."}},"additionalProperties":false,"type":"object","required":["tx_hash","status","status_block"],"title":"LaunchStatus","description":"The newest FAIRMINTER_UPDATE per launch.\n\n★ `status_block` IS THE COLUMN NOTHING ELSE PUBLISHES. `/api/launches` serves\na launch's status (via v_launch, which COALESCEs this view's status with the\nannounce-time one) but NOT the block that status landed in — so \"when did this\nlaunch close\" has been unanswerable from the API."},"LaunchStatusCensus":{"properties":{"launches_with_a_status_update":{"$ref":"#/components/schemas/Measured_int_","description":"Rows in v_fairminter_status."},"launches_announced":{"$ref":"#/components/schemas/Measured_int_","description":"NEW_FAIRMINTER events held. The denominator."},"launches_with_no_status_update":{"$ref":"#/components/schemas/Measured_int_","description":"Announced but never updated — INVISIBLE to this route. For those, the only status that exists is the announce-time one, served by /api/launches."}},"additionalProperties":false,"type":"object","required":["launches_with_a_status_update","launches_announced","launches_with_no_status_update"],"title":"LaunchStatusCensus"},"LaunchStatusEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/LaunchStatus"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/LaunchStatusCensus"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"LaunchStatusEnvelope"},"LaunchTotals":{"properties":{"launches":{"type":"integer","title":"Launches"},"open":{"type":"integer","title":"Open"},"pending":{"type":"integer","title":"Pending"},"closed":{"type":"integer","title":"Closed"},"xcp_escrowed":{"type":"number","title":"Xcp Escrowed"},"minters":{"type":"integer","title":"Minters","description":"⚠️ THE SUM OF THE PER-LAUNCH `minters` COUNTS, NOT A DISTINCT ADDRESS COUNT. An address that minted two different launches is counted TWICE, so this is an upper bound on the number of distinct people and is NOT comparable to /api/mints' `minters` (DISTINCT, and over ALL fairmints rather than XCP-69). With the archive at block 964,058: 819 here vs 3,614 distinct all-fairmint addresses on /api/mints, while the terminal page showed 228 distinct XCP-69 addresses. The distinct XCP-69 figure is `minters_distinct`."},"unknown_conformance":{"type":"integer","title":"Unknown Conformance"},"minters_distinct":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minters Distinct","description":"DISTINCT minting addresses across every XCP-69 launch counted in `launches` (open, pending and closed), over valid mints only: ONE set, so an address that minted several launches is counted ONCE. Built in the same cached read as the rows, so it is as of `as_of_block`. Always <= `minters` (that is a SUM of per-launch counts) and >= the largest single launch's `minters`; a read outside that band is refused with a 503, never published. Not /api/mints' `minters`, which is distinct over ALL fairmints, not only XCP-69."}},"type":"object","required":["launches","open","pending","closed","xcp_escrowed","minters","unknown_conformance"],"title":"LaunchTotals"},"LaunchesEnvelope":{"properties":{"block":{"type":"integer","title":"Block"},"open":{"items":{"$ref":"#/components/schemas/LaunchRow"},"type":"array","title":"Open"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The ARCHIVE block these rows were built at — the archive head, read once per request and used as the cache key, carried inside the cached value so a request that waited on another's build publishes that build's block. ⛔ NOT `block`: `block` is `ingest_health.chain_tip`, the node tip used as the COUNTDOWN reference, and it is an UPPER bound on this — during catch-up the node tip stands still while the archive advances. Age this answer by `as_of_block`."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"Always false on this route: it has no fallback arm (a failed rebuild is a 503, never old rows). ⛔ It is NOT `/api/markets/ranking`'s head comparison (`as_of_block != archive_head`): a request that waited on another request's build can publish an `as_of_block` below the head it read and still say false here. Age this answer by `as_of_block`."},"pending":{"items":{"$ref":"#/components/schemas/LaunchRow"},"type":"array","title":"Pending"},"closed":{"items":{"$ref":"#/components/schemas/LaunchRow"},"type":"array","title":"Closed"},"nonconformant":{"items":{"$ref":"#/components/schemas/LaunchRow"},"type":"array","title":"Nonconformant"},"unknown_conformance":{"items":{"$ref":"#/components/schemas/LaunchRow"},"type":"array","title":"Unknown Conformance"},"totals":{"$ref":"#/components/schemas/LaunchTotals"},"minters_basis":{"anyOf":[{"$ref":"#/components/schemas/LaunchMintersBasis"},{"type":"null"}],"description":"Names the population behind `totals.minters` (a SUM of per-launch counts) and `totals.minters_distinct` (each address once), so the two cannot be confused."},"unknown_conformance_note":{"type":"string","title":"Unknown Conformance Note"},"constants":{"$ref":"#/components/schemas/LaunchConstants"}},"type":"object","required":["block","open","pending","closed","nonconformant","unknown_conformance","totals","unknown_conformance_note","constants"],"title":"LaunchesEnvelope","description":"GET /api/launches.\n\n⚠️ Five sibling lists, ONE row shape. `unknown_conformance` is the third value of\na three-valued answer and is NOT \"nonconformant\" — see /api/launches/conformance."},"LegSupply":{"properties":{"supply":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Supply","description":"⚠️ LOSSY above 2^53 satoshis — see the class note. Kept for existing consumers; prefer `supply_exact`."},"burned":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Burned","description":"⚠️ Lossy twin of `burned_exact`."},"circulating":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Circulating","description":"⚠️ Lossy twin of `circulating_exact`. Values past 2^53 satoshis have been observed on this field."},"locked":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Locked"},"supply_exact":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Supply Exact","description":"Total supply, exact, as a JSON string — PARSE IT AS A DECIMAL, never as a float. Null when `supply` is null, and null in the one further case no string can state: a non-finite figure."},"burned_exact":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Burned Exact","description":"Burn-address holdings, exact, as a JSON string. Null whenever the burn snapshot is unusable OR its figure for this asset is not a real number — never a zero standing in for one; `burn_reason` on the row says which."},"circulating_exact":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Circulating Exact","description":"`supply_exact - burned_exact`, computed in decimal and published as a JSON string. Null whenever either input is."},"supply_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Supply Reason"},"burned_is_lower_bound":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Burned Is Lower Bound","description":"True whenever `burned` is published at all: the figure covers ONLY the curated burn-address list, so it can be too LOW and never too high. Null when `burned` is null — there is no bound on a figure that was never published, and `false` would be a claim we cannot support."},"circulating_is_upper_bound":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Circulating Is Upper Bound","description":"True whenever `circulating` is published at all. It is `supply - burned` with `burned` a lower bound, so it can only be too HIGH — and so can any market cap built on it. Null when `circulating` is null."},"burned_zero_meaning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burned Zero Meaning","description":"⛔ NON-NULL EXACTLY WHEN `burned == 0`, and it says that the zero is NOT a claim that none was burned. THIS IS THE FIELD TO BRANCH ON: do not render `burned == 0` as 'none burned' while this is set. Null when `burned` is positive (no ambiguity to resolve) and null when `burned` is null (`burn_reason` on the row already says why). ⚠️ This archive has NO state meaning 'we verified none was burned' — proving that needs an exhaustive holder scan it does not perform — so this field never turns off to mean the opposite."}},"type":"object","required":["supply","burned","circulating","locked","supply_reason"],"title":"LegSupply","description":"One leg's supply figures (`a` or `b` of a pool row). THE SAME ELEVEN KEYS ON\nEVERY BRANCH. `supply_reason` is null whenever a supply IS published.\n\n⛔ THE `*_exact` TWINS ARE THE ONES TO PARSE; THE FLOATS CANNOT CARRY THE NUMBER.\n  AS OF BLOCK 963,682, DANKMEMECASH's circulating figure was\n  `882537456.50777774` = 88,253,745,650,777,774 satoshis, past 2^53\n  (9,007,199,254,740,992) where a float64 mantissa runs out — as a float it\n  comes back `882537456.5077777`, one digit short. That is a property of the\n  TYPE, not of the arithmetic: computing in Decimal and narrowing once gives the\n  identical wrong answer, so there is no float that can publish this.\n  ⇒ `*_exact` ARE JSON **strings** on the wire, constrained by a decimal\n    pattern that rejects scientific notation. Parse them as decimals.\n  ⇒ The float twins are RETAINED and unchanged so no existing consumer moves.\n    They are the deprecation candidates, not the contract.\n⚠️ `supply_exact` is exact end to end (numeric off a decimal string in our own\n  archive). `burned_exact` is only as exact as the third-party burn snapshot it\n  is read from, and `circulating_exact` inherits that. \"Exact\" here means WE\n  added no error, never that the upstream figure is exact."},"MarketRankingEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/MarketRankingRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"withheld_below_floor":{"type":"integer","title":"Withheld Below Floor"},"vol_window_blocks":{"type":"integer","title":"Vol Window Blocks"},"vol_window_floor_block":{"type":"integer","title":"Vol Window Floor Block"},"membership_depth_blocks":{"type":"integer","title":"Membership Depth Blocks"},"membership_since_block":{"type":"integer","title":"Membership Since Block"},"derived_at_block":{"type":"integer","title":"Derived At Block"},"as_of_block":{"type":"integer","title":"As Of Block"},"archive_head":{"type":"integer","title":"Archive Head"},"stale_fallback":{"type":"boolean","title":"Stale Fallback"},"rebuild_failing":{"type":"boolean","title":"Rebuild Failing"},"book_as_of":{"type":"string","title":"Book As Of"},"absence_means":{"type":"string","title":"Absence Means"},"note":{"type":"string","title":"Note"},"venue":{"type":"string","title":"Venue"},"source":{"type":"string","title":"Source"},"book_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Book Source","description":"Present only while the book is switched to read the `order_state` table (the switch `/api/book` reads). Names the relation the `bids`/`asks` columns came from: `order_state` when the reconciler's trust rule held for this request and for the build that produced these rows; `v_order_open` when it did not, and `book_source_refusal` then says why. Absent entirely when the switch is off, which is the default."},"reconciled_head":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Reconciled Head","description":"Present only while switched. The archive head at which the last reconcile run with verdict `passed` found `order_state` equal to the view it replaces, as read by THIS request's trust check. Null when no run has ever passed or that check could not read the table."},"reconciled_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reconciled At","description":"Present only while switched. When that passed reconcile run was recorded (ISO 8601). Null exactly when `reconciled_head` is null."},"book_source_refusal":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Book Source Refusal","description":"Present only while switched. Null when `order_state` served the book columns. Otherwise `CODE: detail` — the same codes `/api/book` publishes — naming why the table was refused, and the book columns are the view's, exactly as they would be with the switch off."}},"type":"object","required":["result","count","withheld_below_floor","vol_window_blocks","vol_window_floor_block","membership_depth_blocks","membership_since_block","derived_at_block","as_of_block","archive_head","stale_fallback","rebuild_failing","book_as_of","absence_means","note","venue","source"],"title":"MarketRankingEnvelope","description":"GET /api/markets/ranking — the whole ranking, not paged.\n\n⛔ NO `truncated`, NO `next_offset`, NO `ceiling_note`, by design: the ranking\nships as ONE free response, cached per block and rate limited, not paged.\n`count` is the whole population the membership rule admits, so a 200 carries\nthe entire ranking."},"MarketRankingRow":{"properties":{"pair":{"type":"string","title":"Pair"},"base":{"type":"string","title":"Base"},"quote":{"type":"string","title":"Quote"},"last":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last"},"chg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Chg"},"prev_t":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Prev T"},"vol_xcp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vol Xcp"},"vol_quote":{"type":"number","title":"Vol Quote"},"trades":{"type":"integer","title":"Trades"},"trades_all":{"type":"integer","title":"Trades All"},"bids":{"type":"integer","title":"Bids"},"asks":{"type":"integer","title":"Asks"},"has_pool":{"type":"boolean","title":"Has Pool"},"last_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Block"},"last_t":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last T"}},"type":"object","required":["pair","base","quote","last","chg","prev_t","vol_xcp","vol_quote","trades","trades_all","bids","asks","has_pool","last_block","last_t"],"title":"MarketRankingRow","description":"One row of the terminal's market ranking — /api/markets/ranking.\n\n⭐ The fifteen keys are exactly what the terminal page renders, so the page can\nswap its baked array for this response without a render change.\n\n⚠️ THREE NULLS THAT ARE NOT ZEROS. `vol_xcp` is null when the quote is\nneither XCP nor BTC — \"cannot be expressed in XCP\". `chg`/`prev_t` are null\nuntil a pair has TWO fills on record, and `chg` is measured against the\nprevious fill at whatever age, never over 24 h. `last`/`last_block`/`last_t`\nare null for a pair that has never traded and is listed on a resting order or\nan AMM pool alone."},"MarketStatsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/MarketStatsRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"window_days":{"type":"integer","title":"Window Days"},"absence_means":{"type":"string","title":"Absence Means"},"venue":{"type":"string","title":"Venue"},"as_of_block":{"type":"integer","title":"As Of Block","description":"The archive block these rows were BUILT at. After a new archive block, a background refresher rebuilds this route's default request, at most once per minimum gap (60 s by default) measured from the start of its previous rebuild; a request that finds no rows built at the current block yet (for example, for a block that landed inside that gap) builds them itself, waits for a build already running, or — within a short cooldown after a failed build — is answered from the last good rows (`rebuild_failing`). In every case, on a fresh answer this equals `archive_head`; on a `stale_fallback` answer it is the TRUE, older block the last good build ran at — never the live head."},"source":{"type":"string","title":"Source"},"archive_head":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Archive Head","description":"The archive head THIS request read. `as_of_block != archive_head` is exactly `stale_fallback`."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when these rows were NOT built at the archive head THIS request read (`archive_head`). Two causes: a rebuild failed and you were served the LAST GOOD rows instead of a 503; or a new block landed while a build was already running and this request waited for that build rather than starting its own. `rebuild_failing` tells them apart: it is true while a failed build is remembered. A HEAD comparison, the ranking's predicate — read `as_of_block` for the block they are from. ⛔ Not `freshness.stale` (ARCHIVE-vs-CHAIN)."},"rows_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rows Age S","description":"Seconds since the build that produced these rows finished — monotonic, never a timestamp. It matters here because the route's window is WALL-CLOCK (a trailing interval ending when the rows were built): between blocks the window is the build's, up to one block interval old."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route's cache is REMEMBERED right now — the route is inside a labelled-fallback window and declining to re-attempt for a cooldown. ⛔ A THIRD AXIS, NOT A SECOND NAME: the combination it exists for is FRESH + FAILING (a concurrent flight stored rows at this instant while this request's own build was cancelled). ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'."}},"type":"object","required":["result","count","window_days","absence_means","venue","as_of_block","source"],"title":"MarketStatsEnvelope","description":"GET /api/markets/stats."},"MarketStatsRow":{"properties":{"pair":{"type":"string","title":"Pair"},"base":{"type":"string","title":"Base"},"quote":{"type":"string","title":"Quote"},"last":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last"},"last_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Block"},"last_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last At"},"prev":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Prev"},"prev_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prev At"},"chg":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Chg"},"chg_basis":{"type":"string","title":"Chg Basis"},"fills_all":{"type":"integer","title":"Fills All"}},"type":"object","required":["pair","base","quote","last","last_block","last_at","prev","prev_at","chg","chg_basis","fills_all"],"title":"MarketStatsRow","description":"One pair's price stats. `chg_basis` names WHY `chg` is null rather than\nleaving the reader to guess."},"MarketVolumeEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/MarketVolumeRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"window_hours":{"type":"integer","title":"Window Hours"},"truncated":{"type":"boolean","title":"Truncated"},"absence_means":{"type":"string","title":"Absence Means"},"venue":{"type":"string","title":"Venue"},"as_of_block":{"type":"integer","title":"As Of Block"},"source":{"type":"string","title":"Source"},"built_at_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Built At Block","description":"The archive head the cached rows were BUILT under — the head the request that ran the build had read. `as_of_block` beside it keeps its old meaning (the head THIS request read), so on a fallback the two differ and the pair says by how much: the rule is that a fallback carries its TRUE age, never the live head. ⚠️ This route's window is WALL-CLOCK (a trailing interval ending when the rows were built), so `rows_age_s` is the age that bounds it; this field says which archive it was computed against."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when this answer's cached rows are OLDER THAN THE ROUTE'S CACHE TTL — the rebuild did not finish and you were served the LAST GOOD answer instead of a 503. The 200 is then a labelled fallback, not a fresh answer. ⚠️ THE PREDICATE IS THE ROWS' AGE AGAINST THE TTL, not 'the fallback path ran': a concurrent rebuild can finish while this one fails, and that answer is genuinely fresh and is reported so — read `rebuild_failing` for the other axis. ⛔ Not `freshness.stale`, which is ARCHIVE-vs-CHAIN."},"rows_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rows Age S","description":"How old the cached part of this answer is, in seconds, since the build that produced it FINISHED. A DURATION from a monotonic clock, never a wall-clock timestamp, so an ntp step cannot move it. Recovered from the cache's own stamp even when another request did the build."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route's cache is REMEMBERED right now — the route is inside a labelled-fallback window and declining to re-attempt for a cooldown. ⛔ A THIRD AXIS, NOT A SECOND NAME: the combination it exists for is FRESH + FAILING (a concurrent flight stored rows at this instant while this request's own build was cancelled). ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'."}},"type":"object","required":["result","count","window_hours","truncated","absence_means","venue","as_of_block","source"],"title":"MarketVolumeEnvelope","description":"GET /api/markets/volume."},"MarketVolumeRow":{"properties":{"pair":{"type":"string","title":"Pair"},"base":{"type":"string","title":"Base"},"quote":{"type":"string","title":"Quote"},"fills":{"type":"integer","title":"Fills"},"vol_quote":{"type":"number","title":"Vol Quote"},"vol_xcp":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vol Xcp"}},"type":"object","required":["pair","base","quote","fills","vol_quote","vol_xcp"],"title":"MarketVolumeRow","description":"One pair's volume. `vol_xcp` is null when the quote is not XCP — \"cannot be\nexpressed in XCP\", never zero."},"MatchSettlementCensus":{"properties":{"matches_with_an_update":{"$ref":"#/components/schemas/Measured_int_","description":"Rows in v_match_state: every match that received an ORDER_MATCH_UPDATE."},"expired":{"$ref":"#/components/schemas/Measured_int_","description":"Struck, then fell through. Never settled."},"completed":{"$ref":"#/components/schemas/Measured_int_"},"matches_total":{"$ref":"#/components/schemas/Measured_int_","description":"EVERY ORDER_MATCH ever held, the honest denominator. Only populated when include_match_total=true was asked for; otherwise value is null and method says so, because it costs a full pass over the match tape."}},"additionalProperties":false,"type":"object","required":["matches_with_an_update","expired","completed","matches_total"],"title":"MatchSettlementCensus","description":"⛔ THE DENOMINATOR IS THE POINT. `v_match_state` is built from\nORDER_MATCH_UPDATE and therefore sees ONLY matches that needed resolving. A\nmatch that settled on the spot never produced an update and is not in it —\nreading `expired / matches_with_an_update` as the chain's failure rate\noverstates it by more than 9x."},"MatchState":{"properties":{"match_id":{"type":"string","title":"Match Id","description":"Counterparty's match id, verbatim: '<tx0_hash>_<tx1_hash>'."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"'expired' — struck and never settled — or 'completed'. null if an update arrived carrying no status at all, which is a different fact from 'no update'."},"maker_tx":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Maker Tx","description":"tx0_hash, split from match_id. null if the id did not parse."},"taker_tx":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Taker Tx","description":"tx1_hash, split from match_id. null if the id did not parse."}},"additionalProperties":false,"type":"object","required":["match_id","status","maker_tx","taker_tx"],"title":"MatchState","description":"One order match's FINAL settlement state.\n\n★ `maker_tx` / `taker_tx` ARE SPLIT OUT OF `match_id`, AND THE SPLIT IS\nGUARDED. The id is `<tx0_hash>_<tx1_hash>` — AS OF BLOCK 963,675 (2026-08-23)\n23,999 of 23,999 ids match `^[0-9a-f]{64}_[0-9a-f]{64}$`. A row that\ndoes not match returns BOTH halves as null rather than a mis-split hash,\nbecause half a txid that looks like a txid is worse than an admitted absence."},"MatchStateEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/MatchState"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/MatchSettlementCensus"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"MatchStateEnvelope"},"Measured_Decimal_":{"properties":{"value":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Value","description":"The measured number, or null if it was not measured."},"measured_at":{"type":"string","format":"date-time","title":"Measured At","description":"When this number was computed. Never the time of the underlying event."},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this number was computed against."},"method":{"type":"string","title":"Method","description":"How it was derived, in words. Never blank."},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Unit, when it is not a plain count."}},"additionalProperties":false,"type":"object","required":["value","measured_at","method"],"title":"Measured[Decimal]"},"Measured_int_":{"properties":{"value":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Value","description":"The measured number, or null if it was not measured."},"measured_at":{"type":"string","format":"date-time","title":"Measured At","description":"When this number was computed. Never the time of the underlying event."},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this number was computed against."},"method":{"type":"string","title":"Method","description":"How it was derived, in words. Never blank."},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Unit, when it is not a plain count."}},"additionalProperties":false,"type":"object","required":["value","measured_at","method"],"title":"Measured[int]"},"MempoolEnvelope":{"properties":{"scope":{"type":"string","title":"Scope"},"source_node":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Node","description":"Which node told us — NULL when we do not know. A failover partway through a collection cycle makes that cycle a union of two nodes' pending views, and the collector drops the label instead of guessing one. Never read NULL here as 'no node'; read it as 'not attributable to a single node'. ⚠️ AND A NON-NULL VALUE IS NOT A CLAIM THAT ONE NODE WROTE THIS ARCHIVE. It is the MODAL label — the node that recorded the most rows in `mempool_events`, counting live and resolved rows together. A row's label is written once at first sighting and NEVER corrected (later sightings update the row's last-seen time, poll count and outcome, never its label), and nothing purges the table, so after a PERMANENT failover the old node keeps its rows, the new node accumulates its own, and this field names whichever has more — which is history, not necessarily the node answering now. The counts beside it (`observed`, `live`, `resolved`) are aggregated over ALL rows regardless of label."},"observed":{"anyOf":[{"$ref":"#/components/schemas/MempoolObserved"},{"type":"null"}],"description":"The span of this node's sightings. NULL means this node has recorded no mempool sighting at all — there is no observation to describe. It is NOT a claim that the mempool is empty."},"live":{"$ref":"#/components/schemas/MempoolLive"},"resolved":{"$ref":"#/components/schemas/MempoolResolved"},"event":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event","description":"The event-type filter this answer was computed with (default NEW_FAIRMINT; a comma-separated list echoes back joined by commas). ABSENT — not null — when this node has recorded no sighting yet, because no filter was ever applied."},"mints":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Mints"},"note":{"type":"string","title":"Note"},"as_of":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of","description":"max(last_seen_at) — this node's most recent mempool sighting, and the dateline for this LIVE answer. A stale value means the collector has stopped, NOT that the mempool is empty; null means this node has never recorded a sighting. Not a block height, deliberately: see as_of_basis. Declared optional in this schema."},"as_of_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"As Of Basis","description":"States in the payload itself that as_of is a wall clock and not a block height, so a consumer does not have to infer it from the type. Constant text."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when this answer's cached rows are OLDER THAN THE ROUTE'S CACHE TTL — the rebuild did not finish and you were served the LAST GOOD answer instead of a 503. The 200 is then a labelled fallback, not a fresh answer. ⚠️ THE PREDICATE IS THE ROWS' AGE AGAINST THE TTL, not 'the fallback path ran': a concurrent rebuild can finish while this one fails, and that answer is genuinely fresh and is reported so — read `rebuild_failing` for the other axis. ⛔ Not `freshness.stale`, which is ARCHIVE-vs-CHAIN."},"rows_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rows Age S","description":"How old the cached part of this answer is, in seconds, since the build that produced it FINISHED. A DURATION from a monotonic clock, never a wall-clock timestamp, so an ntp step cannot move it. Recovered from the cache's own stamp even when another request did the build."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route's cache is REMEMBERED right now — the route is inside a labelled-fallback window and declining to re-attempt for a cooldown. ⛔ A THIRD AXIS, NOT A SECOND NAME: the combination it exists for is FRESH + FAILING (a concurrent flight stored rows at this instant while this request's own build was cancelled). ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'."}},"type":"object","required":["scope","source_node","observed","live","resolved","mints","note"],"title":"MempoolEnvelope","description":"GET /api/mempool.\n\n⚠️ `mints` is typed as a list of objects and is NOT given a row model: it was\nempty in every sample this schema was derived from, and this schema does not\nguess a row shape.\n⛔ THIS ROUTE HAS THREE 200 SHAPE BRANCHES:\n  1 · healthy      — populated archive, a single known upstream.\n  2 · unattributed — populated archive, `source_node` NULL. ONE upstream\n                     failover is enough to produce it, not a fresh install.\n  3 · empty        — no sighting recorded: `observed` NULL and `event` ABSENT\n                     ENTIRELY.\nA fourth case leaves every key and type untouched: after a PERMANENT failover\n`source_node` names one node over an archive two nodes wrote — see its\ndescription."},"MempoolLive":{"properties":{"distinct_tx":{"type":"integer","title":"Distinct Tx"},"events":{"type":"integer","title":"Events"},"by_event":{"additionalProperties":{"type":"integer"},"type":"object","title":"By Event","description":"Event name -> count. A free-form MAP, not a fixed record: the keys are whatever event types this node currently holds in its own mempool, so naming them would be false the next minute."}},"type":"object","required":["distinct_tx","events","by_event"],"title":"MempoolLive"},"MempoolObserved":{"properties":{"first_seen_earliest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen Earliest"},"last_seen_latest":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen Latest"},"rows_total":{"type":"integer","title":"Rows Total"},"tx_total":{"type":"integer","title":"Tx Total"}},"type":"object","required":["first_seen_earliest","last_seen_latest","rows_total","tx_total"],"title":"MempoolObserved"},"MempoolResolved":{"properties":{"confirmed":{"type":"integer","title":"Confirmed"},"vanished":{"type":"integer","title":"Vanished"}},"type":"object","required":["confirmed","vanished"],"title":"MempoolResolved"},"MintCurveEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/MintCurvePoint"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"launch":{"$ref":"#/components/schemas/LaunchHeader"},"totals":{"$ref":"#/components/schemas/MintCurveTotals"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","launch","totals"],"title":"MintCurveEnvelope"},"MintCurvePoint":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"mints":{"type":"integer","title":"Mints"},"minters_in_block":{"type":"integer","title":"Minters In Block","description":"⚠️ DISTINCT WITHIN THIS BLOCK. Summing this column over blocks does NOT give the launch's distinct minter count."},"paid_quantity_raw":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Paid Quantity Raw","description":"Satoshi-XCP. Divide by 1e8 for XCP."},"paid_xcp":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Paid Xcp","description":"paid_quantity_raw / 1e8. XCP is divisible, so this is correct."},"earned_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Earned Normalized","description":"The node's own normalized figure — correct for INDIVISIBLE assets too."},"first_minted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"First Minted At"},"last_minted_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Minted At"}},"additionalProperties":false,"type":"object","required":["block_index","mints","minters_in_block","paid_quantity_raw","paid_xcp","earned_normalized","first_minted_at","last_minted_at"],"title":"MintCurvePoint"},"MintCurveTotals":{"properties":{"blocks_with_mints":{"$ref":"#/components/schemas/Measured_int_"},"total_mints":{"$ref":"#/components/schemas/Measured_int_"},"total_paid_raw":{"$ref":"#/components/schemas/Measured_Decimal_"},"returned_blocks":{"$ref":"#/components/schemas/Measured_int_","description":"Blocks actually in THIS response, after limit/offset."}},"additionalProperties":false,"type":"object","required":["blocks_with_mints","total_mints","total_paid_raw","returned_blocks"],"title":"MintCurveTotals"},"MintRow":{"properties":{"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Asset"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"tx":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx"},"block":{"type":"integer","title":"Block"},"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"t":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"T"},"xcp":{"type":"number","title":"Xcp"},"tok":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Tok"},"asset_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset Divisible"}},"type":"object","required":["asset","source","tx","block","block_index","event_index","t","xcp","tok","asset_divisible"],"title":"MintRow","description":"One valid fairmint. `xcp` is never null (a missing value reads 0); `tok`\nstays null when it could not be read — \"0 minted\" and \"we could not read it\" are\ndifferent claims. `asset_divisible` may be null."},"MintsEnvelope":{"properties":{"block":{"type":"integer","title":"Block"},"result":{"items":{"$ref":"#/components/schemas/MintRow"},"type":"array","title":"Result"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The ARCHIVE block these rows were built at — the archive head, read once per request and used as the cache key, carried inside the cached value so a request that waited on another's build publishes that build's block. ⛔ NOT `block`: `block` is `ingest_health.chain_tip`, the node tip used as the COUNTDOWN reference, and it is an UPPER bound on this — during catch-up the node tip stands still while the archive advances. Age this answer by `as_of_block`."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"Always false on this route: it has no fallback arm (a failed rebuild is a 503, never old rows). ⛔ It is NOT `/api/markets/ranking`'s head comparison (`as_of_block != archive_head`): a request that waited on another request's build can publish an `as_of_block` below the head it read and still say false here. Age this answer by `as_of_block`."},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Count of EVERY valid fairmint in the archive — all assets, not only XCP-69 launches. Roughly 200x the XCP-69 mint count at block 964,058 (191,244 against 945). Count-verified against the chain for the whole NEW_FAIRMINT population; that verification is what would be destroyed by filtering this route."},"minters":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Minters","description":"⚠️ ONE WORD, THREE POPULATIONS ACROSS THIS SITE. Here it is count(DISTINCT minter) over ALL valid fairmints (3,614 at block 964,058). /api/launches' `totals.minters` is the SUM of per-launch counts over XCP-69 only (819 at the same block, and it double-counts an address that minted two launches). The terminal page shows DISTINCT addresses over XCP-69 only (228 when those two were read). None is wrong; the NAME is. See `minters_basis`."},"showing":{"type":"integer","title":"Showing"},"population":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Population","description":"What the rows and counts on this envelope ARE, in one sentence, as DATA rather than as prose in a description."},"xcp69_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Xcp69 Only","description":"⛔ Always false on this route, and it is a statement about what `v_fairmint` IS — not a filter that might flip. If you need XCP-69 only, use /api/launches; do not filter these rows by hand, because the launch-set join lives in v_launch."},"population_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Population Note","description":"Where to go for the XCP-69 population instead, and why not to reconstruct it from these rows."},"minters_basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Minters Basis","description":"Which of the three `minters` populations this one is."}},"type":"object","required":["block","result","showing"],"title":"MintsEnvelope","description":"GET /api/mints — EVERY VALID COUNTERPARTY FAIRMINT, not only XCP-69.\n\n`v_fairmint` is `NEW_FAIRMINT WHERE status='valid'` with NO join to the XCP-69\nlaunch set, so `total` is far larger than the XCP-69 mint count. The population\nis stated as DATA — `population`, `xcp69_only`, `population_note` — because a\nsentence can rot and a field is data. For XCP-69 only, use /api/launches."},"OrderOutcome":{"properties":{"status":{"type":"string","title":"Status","description":"Counterparty's own status string, verbatim and unedited."},"outcome_class":{"$ref":"#/components/schemas/OutcomeClass"},"orders":{"type":"integer","title":"Orders","description":"Orders whose latest state is this status."},"first_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Block","description":"Earliest opened_block among them."},"last_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Block","description":"Latest opened_block among them."},"measured_at":{"type":"string","format":"date-time","title":"Measured At","description":"These are aggregates. This is when they were computed."}},"additionalProperties":false,"type":"object","required":["status","outcome_class","orders","first_block","last_block","measured_at"],"title":"OrderOutcome","description":"One status string, with how many orders ended there and when it was counted.\n\n⚠️ THE REJECTION REASONS USE U+2010 (HYPHEN), NOT AN ASCII HYPHEN-MINUS.\n`status LIKE '%non-positive%'` typed normally matches ZERO rows while thousands\nexist. Match on the string this route returns, byte for byte."},"OrderOutcomeCensus":{"properties":{"orders_total":{"$ref":"#/components/schemas/Measured_int_","description":"Every order in scope, all classes summed."},"rejected_total":{"$ref":"#/components/schemas/Measured_int_"},"distinct_statuses":{"$ref":"#/components/schemas/Measured_int_"},"distinct_rejection_reasons":{"$ref":"#/components/schemas/Measured_int_"}},"additionalProperties":false,"type":"object","required":["orders_total","rejected_total","distinct_statuses","distinct_rejection_reasons"],"title":"OrderOutcomeCensus","description":"The denominators. ⛔ Computed from the SAME single pass that produced the\nrows, so they cannot disagree with them the way two queries can."},"OrderOutcomeEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/OrderOutcome"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/OrderOutcomeCensus"},"status_semantics":{"$ref":"#/components/schemas/OrderStatusSemantics"},"stale_fallback":{"type":"boolean","title":"Stale Fallback","description":"True when the census rebuild did not finish and you were served the LAST GOOD totals instead. The 200 is then a labelled fallback, not a fresh answer. ⚠️ THE PREDICATE IS THE ROWS' AGE AGAINST THE CACHE TTL, not 'the fallback code path ran': a concurrent rebuild can finish while this one is failing, and that answer is genuinely fresh and is reported so. ⚠️ ALWAYS false on a request that passed `from_block`/`to_block`, because a windowed census is NOT cached — see the route's own note on why the key may not come from the URL.","default":false},"rows_age_s":{"type":"number","title":"Rows Age S","description":"How old this census is, in seconds: the elapsed time since the single GROUP BY pass that produced it finished. A DURATION from a monotonic clock, never a wall-clock timestamp, so an ntp step cannot move it. 0.0 means this request's own pass had just completed — which is always the case for a windowed (uncached) request. Every row's `measured_at` is derived from this, so the stamps beside the numbers cannot disagree with it.","default":0.0},"rebuild_failing":{"type":"boolean","title":"Rebuild Failing","description":"True when a build failure for this route's cached census is REMEMBERED right now — the route is inside a labelled-fallback window and is declining to re-attempt for a cooldown. ⛔ A THIRD AXIS, NOT A SECOND NAME. `stale_fallback` is the census's AGE against the cache TTL; this is DID-THE-BUILD-WORK, and the combination that motivated the field is FRESH + FAILING: the fallback arm re-reads the cache slot, so a concurrent flight can have stored a census AT this instant — `rows_age_s` 0.0, `stale_fallback` false — while this request's own pass was being cancelled and its log line said THE ROUTE IS NOT HEALTHY. Before this field the log and the wire contradicted each other in the same request and the wire was the one that read healthy. ⚠️ ALWAYS false on a request that passed `from_block`/`to_block`, for the same reason `stale_fallback` is: a windowed census is built by that request and reads no cache, so there is no fallback for it to be inside. ⛔ It is NOT `freshness.stale` / `stale_reason_codes`, which are ARCHIVE-vs-CHAIN. Three axes, three names. ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'.","default":false}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census","status_semantics"],"title":"OrderOutcomeEnvelope","description":"⛔ `stale_fallback` and `rows_age_s` are how a reader tells a current census\nfrom a labelled older one: neither the status code nor the latency carries that\nsignal, because a last-good answer is a fast 200.\n⚠️ SAME NAMES AS `/api/pools` AND `/api/book`, deliberately: it is the same\nCACHE-vs-ARCHIVE axis, and it is NOT `freshness.stale` / `stale_reason_codes`,\nwhich are stable API on the metrics surface and mean ARCHIVE-vs-CHAIN. Both\nappear on this response and they can disagree without either being wrong."},"OrderState":{"properties":{"opened_block":{"type":"integer","title":"Opened Block","description":"The block the OPEN_ORDER confirmed in."},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"type":"string","title":"Tx Hash","description":"The order's own transaction hash."},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source","description":"The address that placed the order."},"give_asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Give Asset"},"get_asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Get Asset"},"give_quantity":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Quantity","description":"RAW opening size. See the class note."},"get_quantity":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Get Quantity"},"give_remaining":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining","description":"RAW, from the LAST ORDER_UPDATE."},"get_remaining":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Get Remaining","description":"RAW, from the LAST ORDER_UPDATE that carried remainings. ⛔ THIS FIELD CAN BE NEGATIVE, AND A NEGATIVE IS NOT AN ERROR. On 2026-08-24, 73 of 200 rows on one page of /api/orders carried a negative value, every one of them status='filled'. The sign is CHAIN TRUTH: Counterparty's own ORDER_UPDATE params carry it and this archive publishes what the chain said. Read `-265193370` as 'this order was consumed past the quantity it asked for'. ⚠️ IF YOU DO ARITHMETIC ON THIS FIELD, CLAMP OR BRANCH: `filled = get_quantity - get_remaining` OVERSHOOTS `get_quantity` on these rows, and any percentage built on it exceeds 100%. See this response's `caveats` for the mechanism."},"status":{"type":"string","title":"Status","description":"The order's latest state: filled / expired / cancelled / open, or an 'invalid: ...' rejection reason straight from Counterparty. ⚠️ Read status_semantics on the envelope before treating 'open' as resting."},"expire_index":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expire Index","description":"null means the order never expires. Measured: that is the majority case for the resting book, not an edge case."},"opened_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Opened At","description":"From the OPEN_ORDER's own block_time."},"give_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Give Divisible"},"get_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Get Divisible"},"give_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Quantity Normalized","description":"The NODE's own normalized opening size — read, not derived."},"get_quantity_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Get Quantity Normalized"},"give_remaining_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Give Remaining Normalized","description":"DERIVED, because ORDER_UPDATE carries no normalized remaining. null when divisibility is unknown: a quantity we cannot scale is one we must not publish as if we could."},"get_remaining_normalized":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Get Remaining Normalized","description":"DERIVED the same way as its give twin, and null under the same condition. ⛔ THE SIGN PROPAGATES AND THAT IS CORRECT, NOT A SECOND DEFECT: normalizing is a divide by 1e8 (divisible) or by nothing (indivisible), and division by a positive constant preserves sign. A negative `get_remaining` therefore appears here as a negative too WHENEVER THIS FIELD HAS A VALUE AT ALL: when divisibility is unknown this field is NULL, not negative, so test for null first. Everything the raw field's description says about arithmetic applies here, and this is the field you should be doing that arithmetic on."}},"additionalProperties":false,"type":"object","required":["opened_block","event_index","tx_hash","source","give_asset","get_asset","give_quantity","get_quantity","give_remaining","get_remaining","status","expire_index","opened_at","give_divisible","get_divisible","give_quantity_normalized","get_quantity_normalized","give_remaining_normalized","get_remaining_normalized"],"title":"OrderState","description":"One order, opened once, in whatever state it ended up in.\n\n⚠️ `*_remaining` ARE RAW CHAIN INTEGERS; `*_remaining_normalized` are the\ndivisibility-adjusted twins and are the ONLY ones safe to do arithmetic on.\nServing raw where normalized was meant is a 1e8 error and this project has\nshipped \"1,163.4353 BITCORN\" once already.\n⛔⛔ A REMAINDER CAN BE NEGATIVE. Not a null, not a zero — a NEGATIVE, on a row\nthat says `status: filled`. It is what the chain said, and this archive\npublishes what the chain said; both remaining fields' descriptions and the\nroute's `caveats` say so. ⇒ If you subtract this from an opening quantity,\nCLAMP.\n⚠️ `give_divisible` / `get_divisible` are READ from the chain, never assumed.\nnull means the flag was absent — it does NOT mean divisible."},"OrderStateEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/OrderState"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"status_semantics":{"$ref":"#/components/schemas/OrderStatusSemantics"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","status_semantics"],"title":"OrderStateEnvelope"},"OrderStatusSemantics":{"properties":{"nothing_remaining_rule_applied":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Nothing Remaining Rule Applied","description":"True when the deployed v_order_state reclassifies a status='open' order with a zero remaining as 'filled'. False when it does not, in which case status='open' OVER-COUNTS the resting book — use /api/book, which applies the rule itself, for the book. null when the probe could not run."},"probe":{"type":"string","title":"Probe","description":"The exact check that produced the field above, in words. Never blank."},"measured_at":{"type":"string","format":"date-time","title":"Measured At","description":"When the probe ran. Not the time of any event."}},"additionalProperties":false,"type":"object","required":["nothing_remaining_rule_applied","probe","measured_at"],"title":"OrderStatusSemantics","description":"WHICH DEFINITION OF `status` THE DATABASE ANSWERING THIS REQUEST IS RUNNING.\n\n⛔ THIS IS A MEASUREMENT, NOT A CONFIGURATION FLAG. Nothing sets it; the route\nasks the database and reports what it said."},"OrphanEvent":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"event":{"type":"string","title":"Event","description":"The event type, e.g. NEW_BLOCK."},"tx_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tx Hash"},"orphaned_block_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Orphaned Block Hash","description":"The hash this event was recorded under."},"canonical_block_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Canonical Block Hash","description":"The hash at that height now. null means this archive holds no canonical block there at all, which is itself a finding."},"orphaned_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Orphaned At","description":"When WE copied the row out. Not chain time."},"fetched_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Fetched At","description":"When WE originally fetched it."},"source_node":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Node"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params","description":"The event's full payload. Only present when include_params=true; null otherwise, which means 'not requested', never 'empty'. ⛔ `BLOCK_PARSED.params.messages_hash` IS NODE-LOCAL AND TIME-VARYING — it is NOT a consensus value and it is NOT comparable across nodes. A mismatch on this one key against another node is NOT evidence that this archive is wrong; it is a property of the field. Compared over 22 strided heights (280,000-963,400, 364 event rows, field by field, two independent operators): the event SETS agree everywhere, and so do the two CONSENSUS hashes `ledger_hash` and `txlist_hash` — `messages_hash` is the ONLY key both nodes emit and disagree on. In a whole-population count over blocks 952,800-963,459, 7,407 of those 10,660 BLOCK_PARSED rows held a value NO rostered node served, not even the same build that produced it. ⇒ Do not diff it, do not reconcile against it, and do not derive from it: nothing in this API reads it. ⚠️ AND THIS ROW CANNOT TELL YOU WHICH BUILD PRODUCED THE VALUE. Provenance is RECORDED PER ROW IN THE ARCHIVE — `events.source_node`, `events.node_version`, `events.node_commit` name the node and the build that wrote a row, where they were recorded (rows ingested before these columns existed carry none). Which build wrote a row is recorded, but it is NOT PUBLISHED HERE: `node_version` / `node_commit` are published by no route at all, so nothing in this response attributes this value to a build. The only provenance published anywhere in this API is `MempoolEnvelope.source_node`, `OrphanEvent.source_node`. On a row of the orphaned-event log, the `source_node` beside this payload is that row's own, copied from the archive when the event was orphaned (null where none was recorded), and it names the NODE, never the build; an archived event row outside that log carries no provenance field at all, and the mempool's `source_node` describes mempool sightings, not archived events. Publishing the build per row would be a response-shape change, which this API has declined, so the honest answer is that the build attribution exists in the archive and you cannot read it from here. ⇒ That is the reason to disregard a mismatch on this key rather than to reconcile it."}},"additionalProperties":false,"type":"object","required":["block_index","event_index","event","tx_hash","orphaned_block_hash","canonical_block_hash","orphaned_at","fetched_at","source_node"],"title":"OrphanEvent","description":"One event this archive copied out of a block the chain later took back,\nbeside the hash of the block that replaced it.\n\n⛔ BOTH HASHES, ALWAYS. An orphan record with only one hash cannot be checked\nby anybody: the whole claim is that these two differ at the same height."},"OrphanEventCensus":{"properties":{"preserved_rows":{"$ref":"#/components/schemas/Measured_int_","description":"Every row in events_orphaned, INCLUDING re-fetches of a block whose hash never changed. The superset this route filters."},"diverged_rows":{"$ref":"#/components/schemas/Measured_int_","description":"Rows where the height's hash actually differs — what this route returns."},"distinct_forks":{"$ref":"#/components/schemas/Measured_int_","description":"From v_orphaned_fork. null if that view is not present."}},"additionalProperties":false,"type":"object","required":["preserved_rows","diverged_rows","distinct_forks"],"title":"OrphanEventCensus"},"OrphanEventEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/OrphanEvent"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"census":{"$ref":"#/components/schemas/OrphanEventCensus"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","census"],"title":"OrphanEventEnvelope"},"OutcomeClass":{"type":"string","enum":["rejected","filled","expired","cancelled","open","other"],"title":"OutcomeClass","description":"⛔ ASSIGNED POSITIVELY, NEVER AS \"not one of the others\". `rejected` is the\nclass for every status Counterparty prefixes with `invalid:`; the four named\nlifecycle states get their own class; anything else this archive has never seen\nlands in `other` and is VISIBLE rather than silently folded into a neighbour."},"PoolDepositRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx":{"type":"string","title":"Tx"},"tx_index":{"type":"integer","title":"Tx Index"},"provider":{"type":"string","title":"Provider"},"asset_a":{"type":"string","title":"Asset A"},"asset_b":{"type":"string","title":"Asset B"},"quantity_a":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity A"},"quantity_a_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity A Normalized"},"asset_a_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset A Divisible"},"quantity_b":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity B"},"quantity_b_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity B Normalized"},"asset_b_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset B Divisible"},"lp_quantity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lp Quantity"},"lp_quantity_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lp Quantity Normalized"},"lp_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Lp Divisible"},"lp_quantity_field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lp Quantity Field"},"status":{"type":"string","title":"Status"},"deposited_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Deposited At"}},"type":"object","required":["block_index","event_index","tx","tx_index","provider","asset_a","asset_b","quantity_a","quantity_a_normalized","asset_a_divisible","quantity_b","quantity_b_normalized","asset_b_divisible","lp_quantity","lp_quantity_normalized","lp_divisible","lp_quantity_field","status","deposited_at"],"title":"PoolDepositRow","description":"One LP deposit. The timestamp key is `deposited_at` here and `withdrawn_at`\non withdrawals, which is why the two are separate row schemas."},"PoolDepositsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/PoolDepositRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"matching":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matching"},"has_more":{"type":"boolean","title":"Has More"},"venue":{"type":"string","title":"Venue"},"filter":{"type":"string","title":"Filter"},"units":{"type":"string","title":"Units"},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","count","matching","has_more","venue","filter","units","source"],"title":"PoolDepositsEnvelope","description":"GET /api/pool/deposits."},"PoolReserveEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/PoolReservePoint"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"points_without_time":{"$ref":"#/components/schemas/Measured_int_","description":"Rows in THIS response whose changed_at is null — a time-axis chart drops them."}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","points_without_time"],"title":"PoolReserveEnvelope"},"PoolReservePoint":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"asset_a":{"type":"string","title":"Asset A"},"asset_b":{"type":"string","title":"Asset B"},"reserve_a":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Reserve A","description":"NORMALIZED. The node did the divisibility work."},"reserve_b":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Reserve B"},"reserve_a_raw":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Reserve A Raw","description":"The raw on-chain integer, kept beside the normalized twin."},"reserve_b_raw":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Reserve B Raw"},"via":{"type":"string","title":"Via","description":"'OPEN_POOL' (pool creation) or 'POOL_UPDATE' (a reserve change)."},"changed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Changed At","description":"From block_times. Null where the block has no timestamp."}},"additionalProperties":false,"type":"object","required":["block_index","event_index","asset_a","asset_b","reserve_a","reserve_b","reserve_a_raw","reserve_b_raw","via","changed_at"],"title":"PoolReservePoint"},"PoolRow":{"properties":{"asset_a":{"type":"string","title":"Asset A"},"asset_b":{"type":"string","title":"Asset B"},"reserve_a":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Reserve A"},"reserve_b":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Reserve B"},"reserves_known":{"type":"boolean","title":"Reserves Known"},"reserve_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reserve Source"},"tx":{"type":"string","title":"Tx"},"opened_block":{"type":"integer","title":"Opened Block"},"lp_asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lp Asset","description":"The pool's LP token, from its OPEN_POOL birth event — the same row `tx` and `opened_block` come from, so it is a fact about the pool's CREATION, never its current state. Null only if that event carries no `lp_asset`. Declared optional in this schema; the handler always emits it."},"creator":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Creator","description":"The address that opened the pool, from OPEN_POOL's `source`. ⛔ NOT to be confused with this row's `supply_source`, `burn_source` and `reserve_source`, which are PROVENANCE strings and not addresses. Declared optional in this schema; the handler always emits it."},"last_change_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Change Block"},"changed_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Changed At"},"quote_asset":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Asset"},"swaps_all":{"type":"integer","title":"Swaps All"},"swaps_24h":{"type":"integer","title":"Swaps 24H"},"vol_quote_all":{"type":"number","title":"Vol Quote All"},"vol_quote_24h":{"type":"number","title":"Vol Quote 24H"},"vol_xcp_all":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vol Xcp All"},"vol_xcp_24h":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Vol Xcp 24H"},"last_swap_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Swap Block"},"last_swap_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Swap At"},"last_swap_price":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Last Swap Price"},"chg_24h":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Chg 24H"},"chg_24h_basis":{"type":"string","title":"Chg 24H Basis"},"fee_bps_min":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fee Bps Min"},"fee_bps_max":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fee Bps Max"},"a":{"$ref":"#/components/schemas/LegSupply"},"b":{"$ref":"#/components/schemas/LegSupply"},"divisible_a":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Divisible A"},"divisible_b":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Divisible B"},"supply_source":{"type":"string","title":"Supply Source"},"burn_ok":{"type":"boolean","title":"Burn Ok"},"burn_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burn Reason"},"burn_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burn Source"},"burn_fetched_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Burn Fetched At"},"burn_addresses":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Burn Addresses"},"burn_addresses_counted":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Burn Addresses Counted","description":"How many addresses in this snapshot COUNT TOWARD BURN TOTALS AT ALL — the subset of `burn_addresses` the snapshot marks `counts_as_burn`. ⛔ SNAPSHOT-WIDE, NOT PER-ASSET, AND NOT PER-ROW. This row has TWO LEGS with TWO DIFFERENT ASSETS, and each leg's `burned` is summed over only the counting addresses that actually HOLD THAT ASSET — which is nearly always far fewer. ⚠️ SO DO NOT READ THIS AS THE COVERAGE OF EITHER LEG'S `burned`. It is a property of the SNAPSHOT: how wide the curated list was when it was taken. Use it against `burn_addresses` (the watched total) and nothing else. Null on the burn-failure path, exactly like `burn_source` and `burn_fetched_at`. Declared optional in this schema; the handler always emits it."},"burn_addresses_are_a_curated_list":{"type":"boolean","title":"Burn Addresses Are A Curated List"},"burn_list_caveat":{"type":"string","title":"Burn List Caveat"}},"type":"object","required":["asset_a","asset_b","reserve_a","reserve_b","reserves_known","reserve_source","tx","opened_block","last_change_block","changed_at","quote_asset","swaps_all","swaps_24h","vol_quote_all","vol_quote_24h","vol_xcp_all","vol_xcp_24h","last_swap_block","last_swap_at","last_swap_price","chg_24h","chg_24h_basis","fee_bps_min","fee_bps_max","a","b","divisible_a","divisible_b","supply_source","burn_ok","burn_reason","burn_source","burn_fetched_at","burn_addresses","burn_addresses_are_a_curated_list","burn_list_caveat"],"title":"PoolRow","description":"One AMM pool.\n\n`vol_xcp_all` / `vol_xcp_24h` are this pool's volume in XCP, all-time and over\nthe trailing 24 hours, and each has three cases. 0.0: the pool had no swap in\nthat window — a measured zero, whatever its quote asset. The quote volume\nitself: the quote asset is XCP. null: the pool swapped in a quote asset that\ncannot be expressed in XCP; or the zero is NOT confirmed — the swap figures\nin this response hold no row for the pool while its reserves last moved at a\nblock those figures did not reach, and `chg_24h_basis` then reads\n`swap-row-not-in-this-window-build`. So a non-XCP pool with no swaps reads\n0.0, not null.\n`changed_at` is the block time of `last_change_block`, the pool's last reserve\nchange. POOL_UPDATE carries no timestamp of its own, so the time is looked up\nby block, and it is null when that block's time is not known here or when no\nreserve change is recorded.\n⚠️ `burn_source`/`burn_fetched_at`/`burn_addresses` are always present, and null\non the burn-failure path — never absent."},"PoolSwapRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx_hash":{"type":"string","title":"Tx Hash"},"swapper":{"type":"string","title":"Swapper"},"asset_a":{"type":"string","title":"Asset A"},"asset_b":{"type":"string","title":"Asset B"},"received_asset":{"type":"string","title":"Received Asset"},"received_quantity":{"type":"number","title":"Received Quantity"},"paid_asset":{"type":"string","title":"Paid Asset"},"paid_quantity":{"type":"number","title":"Paid Quantity"},"received_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Received Divisible"},"paid_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Paid Divisible"},"fee_quantity":{"type":"number","title":"Fee Quantity"},"fee_bps":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Fee Bps"},"status":{"type":"string","title":"Status"},"swapped_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Swapped At"}},"type":"object","required":["block_index","event_index","tx_hash","swapper","asset_a","asset_b","received_asset","received_quantity","paid_asset","paid_quantity","received_divisible","paid_divisible","fee_quantity","fee_bps","status","swapped_at"],"title":"PoolSwapRow","description":"One AMM swap. ⚠️ `tx_hash` here, NOT `tx` as on the book and LP tapes."},"PoolSwapsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/PoolSwapRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"matching":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matching"},"has_more":{"type":"boolean","title":"Has More"},"venue":{"type":"string","title":"Venue"},"filter":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filter","description":"WHICH FILTER ACTUALLY RAN, including 'none — the whole tape, unfiltered'. The LP tapes have published this since they shipped; the swap tape gained it with the asset_a/asset_b pair filter, because before those params existed an unknown query param was ACCEPTED AND SILENTLY IGNORED — /api/pool/swaps?asset_a=XCP answered HTTP 200 with every row, unfiltered. A 200 is not evidence a filter ran. Declared optional in this schema; the handler always emits it."},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","count","matching","has_more","venue","source"],"title":"PoolSwapsEnvelope","description":"GET /api/pool/swaps. No `units` key, unlike the LP tapes."},"PoolWithdrawalRow":{"properties":{"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"tx":{"type":"string","title":"Tx"},"tx_index":{"type":"integer","title":"Tx Index"},"provider":{"type":"string","title":"Provider"},"asset_a":{"type":"string","title":"Asset A"},"asset_b":{"type":"string","title":"Asset B"},"quantity_a":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity A"},"quantity_a_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity A Normalized"},"asset_a_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset A Divisible"},"quantity_b":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity B"},"quantity_b_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Quantity B Normalized"},"asset_b_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Asset B Divisible"},"lp_quantity":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lp Quantity"},"lp_quantity_normalized":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Lp Quantity Normalized"},"lp_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Lp Divisible"},"lp_quantity_field":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lp Quantity Field"},"status":{"type":"string","title":"Status"},"withdrawn_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Withdrawn At"}},"type":"object","required":["block_index","event_index","tx","tx_index","provider","asset_a","asset_b","quantity_a","quantity_a_normalized","asset_a_divisible","quantity_b","quantity_b_normalized","asset_b_divisible","lp_quantity","lp_quantity_normalized","lp_divisible","lp_quantity_field","status","withdrawn_at"],"title":"PoolWithdrawalRow"},"PoolWithdrawalsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/PoolWithdrawalRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"matching":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matching"},"has_more":{"type":"boolean","title":"Has More"},"venue":{"type":"string","title":"Venue"},"filter":{"type":"string","title":"Filter"},"units":{"type":"string","title":"Units"},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","count","matching","has_more","venue","filter","units","source"],"title":"PoolWithdrawalsEnvelope","description":"GET /api/pool/withdrawals."},"PoolsArchiveFreshness":{"properties":{"age_s":{"type":"number","title":"Age S"},"stale_fallback":{"type":"boolean","title":"Stale Fallback"},"built_at_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Built At Block","description":"The head half of the stamp these rows were BUILT at — not the envelope's `as_of_block`, which dates the REQUEST. ⛔ NOT THE PREDICATE: `stale_fallback` can be true while this equals `as_of_block`, and that combination means THE BACKFILL MOVED. Never infer staleness from the head alone. Can be BEHIND a reorg'd chain or AHEAD of the archive after one."},"keyed_on":{"type":"string","title":"Keyed On"},"rebuild_failing":{"type":"boolean","title":"Rebuild Failing"},"rebuilding":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuilding","description":"True when this half was served from its last good build because a rebuild of it is running in the background — usually for a few seconds after a new block. `stale_fallback` is then true as well, and `built_at_block` and `age_s` date the build that was served. A rebuild that FAILED is `rebuild_failing`, not this. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["age_s","stale_fallback","built_at_block","keyed_on","rebuild_failing"],"title":"PoolsArchiveFreshness","description":"`freshness.archive` — the half carrying pools, supply and reserves.\n\n⚠️ ITS PREDICATE IS A STAMP, NOT AN AGE. `stale_fallback` here is\n`built stamp != current stamp`, where the stamp is\n(archive head, backfill fingerprint) — an EXACT statement about which\narchive state these rows describe. So a body can carry `age_s: 600` with\n`stale_fallback: false` and be perfectly correct: nothing has changed."},"PoolsEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/PoolRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"as_of_block":{"type":"integer","title":"As Of Block"},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"True when the pool rows in this response finished building at least 60 s ago — this route's cache TTL. ⛔ IT IS AN AGE COMPARISON AND NOT A REPORT ON THE REBUILD: a rebuild that SUCCEEDED but took longer than the TTL reads true, and a fallback whose re-read found rows a concurrent rebuild had just stored reads false. The 200 is then a LABELLED fallback and this field is the label — it states the rows' age, never the outcome of a build. Read `rows_age_s` for that age and `rebuild_failing` for whether the rebuild is broken. ⛔ THIS IS THE CACHE-vs-ARCHIVE AXIS and it is NOT `freshness.stale` / `stale_reason_codes`, which are stable API on the metrics surface and describe ARCHIVE-vs-CHAIN: a response can carry stale_fallback=true while the archive itself is level with the chain, which is the normal shape of this outage. It is the same field name /api/book publishes, deliberately, because it is the same axis. ⚠️ THE PREDICATE IS THE ROWS' AGE AGAINST THE CACHE TTL, not 'the fallback code path ran': a concurrent rebuild can finish while this one is failing, and that fallback answer is genuinely fresh and is reported so. ⚠️ IT IS NOT DERIVABLE FROM `as_of_block`, unlike /api/book's: this route caches on the wall clock, so its rows carry no block height of their own — read `rows_age_s`. Declared optional in this schema; the handler always emits it."},"rows_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rows Age S","description":"How old the pool rows are, in seconds: the elapsed time since the build that produced THESE rows finished, measured from the stamp the cache itself wrote — including where a concurrent request was handed somebody else's completed build. A DURATION from a monotonic clock, never a wall-clock timestamp, so it cannot be moved by an ntp step. 0.0 means this request's own build had just completed. Compare it to the 60 s cache TTL: `stale_fallback` is exactly `rows_age_s >= 60`. Declared optional in this schema; the handler always emits it."},"rebuild_failing":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuild Failing","description":"True when a build failure for this route is REMEMBERED right now — the route is inside a labelled-fallback window and is declining to re-attempt for a cooldown. ⛔ THIS IS A DIFFERENT QUESTION FROM `stale_fallback` AND THE TWO COME APART. `stale_fallback` is the ROWS' AGE against the cache TTL; this is DID-THE-BUILD-WORK. The combination that motivated the field is FRESH + FAILING, and on this route it is MORE reachable than on /api/book: the fallback arm re-reads the cache slot, so a concurrent flight can have stored rows at this instant — `rows_age_s` 0.0, `stale_fallback` false — while THIS request's own build was being cancelled and its log line said THE ROUTE IS NOT HEALTHY. ⛔ It is NOT `freshness.stale` / `stale_reason_codes` either — those are ARCHIVE-vs-CHAIN. Three axes, three names. ⚠️ A process RESTART clears the memory with no transition, so `false` means 'nothing remembered in THIS process'. ⛔ IT IS AN `or` ACROSS BOTH CACHE KEYS and never under-states; read `freshness` to learn WHICH half is broken."},"rebuilding":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuilding","description":"True when at least one of this body's two halves was served from its last good build because its rebuild is running in the background — the normal state for a few seconds after a new block. It explains a `stale_fallback: true`; it is never set on a fresh body. A rebuild that failed is `rebuild_failing`, not this. Read `freshness` to see which half, and how old the build it was served from is. Declared optional in this schema; the handler always emits it."},"freshness":{"anyOf":[{"$ref":"#/components/schemas/PoolsFreshness"},{"type":"null"}],"description":"Per-half staleness. The three summary fields above are the WORST of the two halves and keep their meaning; this is the decomposition, never a replacement. Declared optional in this schema; the handler always emits it."},"rebuild_health":{"anyOf":[{"$ref":"#/components/schemas/PoolsRebuildHealthPair"},{"type":"null"}],"description":"Per-half rebuild health over a WINDOW. `rebuild_failing` is an instant and cannot be trended; this is what an operator alerts on — gated on `observed_s`. Declared optional in this schema; the handler always emits it."},"halves":{"anyOf":[{"$ref":"#/components/schemas/PoolsHalves"},{"type":"null"}],"description":"What the merge of the two caches did. `window_pairs_unmatched` is normally 0; non-zero means the pool list is one build behind its own population. Declared optional in this schema; the handler always emits it."},"pools_total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pools Total","description":"⚠️ THE POPULATION, where `count` is THIS PAGE. Their equality today is a measurement of the data, not a property of the code, which is exactly why both are published. Declared optional in this schema; the handler always emits it."},"truncated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Truncated","description":"True when the row cap OR the byte budget cut this page short. Declared optional in this schema; the handler always emits it."},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Where to resume, or null when nothing was cut. ⚠️ `offset + rows RETURNED`, never `offset + limit`: when the BYTE budget cut the page short those differ and the latter would silently skip the rows the budget refused. Declared optional in this schema; the handler always emits it."},"ceiling_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ceiling Note","description":"Why this page was cut, or null when it was not — a constant string stops meaning anything. ⛔ A statement about THIS RESPONSE, never about the archive. Declared optional in this schema; the handler always emits it."},"source":{"type":"string","title":"Source"},"note":{"type":"string","title":"Note"}},"type":"object","required":["result","count","as_of_block","source","note"],"title":"PoolsEnvelope","description":"GET /api/pools. `as_of_block` can be -1."},"PoolsFreshness":{"properties":{"archive":{"$ref":"#/components/schemas/PoolsArchiveFreshness"},"window":{"$ref":"#/components/schemas/PoolsWindowFreshness"},"note":{"type":"string","title":"Note"}},"type":"object","required":["archive","window","note"],"title":"PoolsFreshness","description":"`freshness` — /api/pools is assembled from TWO caches, so it has TWO ages.\n\n⛔ THE TOP-LEVEL `stale_fallback` / `rows_age_s` ARE THE WORST OF THE TWO,\nNEVER AN AVERAGE, and they keep their old meaning. This block is the\nDECOMPOSITION: without it a consumer cannot tell a stale 24-hour window\n(harmless, 60 s deep) from a stale supply table."},"PoolsHalves":{"properties":{"archive_pools":{"type":"integer","title":"Archive Pools"},"window_pairs":{"type":"integer","title":"Window Pairs"},"pools_with_swap_row":{"type":"integer","title":"Pools With Swap Row"},"pools_without_swap_row":{"type":"integer","title":"Pools Without Swap Row"},"pools_without_swap_row_unconfirmed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pools Without Swap Row Unconfirmed","description":"How many of `pools_without_swap_row` are pools whose reserves moved ABOVE `window_tape_head` — so the window build could not have seen them swap and their \"never swapped\" is NOT a measurement. Those rows publish `chg_24h_basis: \"swap-row-not-in-this-window-build\"` and a NULL `vol_xcp_all` rather than a measured 0.0. Normally 0. ⛔ It does NOT catch an arbitrary single window row going missing — only the lag produced by the two caches being invalidated differently."},"window_tape_head":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Window Tape Head","description":"The highest POOL_MATCH block the WINDOW build observed — the height the two halves are compared at. Null when that build saw no swaps at all. Pair it with `freshness.archive.built_at_block`: the two halves are routinely NOT level, and that is the design rather than a fault."},"window_pairs_unmatched":{"type":"integer","title":"Window Pairs Unmatched"}},"type":"object","required":["archive_pools","window_pairs","pools_with_swap_row","pools_without_swap_row","window_pairs_unmatched"],"title":"PoolsHalves","description":"`halves` — what the merge of the two caches actually did, published.\n\n⛔ `window_pairs_unmatched` IS THE ONE THAT MATTERS AND IT IS NORMALLY 0.\nNon-zero means the swap tape knows a pair the pool list does not — a pool\nopened between the two cache builds — so the list in hand is one build behind\non its OWN population. Bounded (one block) and DISCLOSED, because the\nalternative is a reader concluding \"no such pool\" from a body that has simply\nnot caught up."},"PoolsRebuildHealth":{"properties":{"failing_now":{"type":"boolean","title":"Failing Now"},"outages":{"type":"integer","title":"Outages"},"armed_s":{"type":"number","title":"Armed S"},"armed_exceeds_observed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Armed Exceeds Observed","description":"⚠️ Normally false. True means a ledger entry claims to predate the process, so `armed_fraction` has been CLAMPED to 1.0 and is not a health reading — the unclamped `armed_s` and `observed_s` beside it are. It is published rather than clamped silently."},"armed_fraction":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Armed Fraction","description":"`armed_s / observed_s`, clamped to [0, 1] — see `armed_exceeds_observed`. ⚠️ NULL, never 0.0, when nothing has been observed yet — 'what fraction of nothing' has no answer and a zero there reads as a healthy hour."},"longest_armed_s":{"type":"number","title":"Longest Armed S"},"window_s":{"type":"number","title":"Window S"},"observed_s":{"type":"number","title":"Observed S"}},"type":"object","required":["failing_now","outages","armed_s","armed_fraction","longest_armed_s","window_s","observed_s"],"title":"PoolsRebuildHealth","description":"One key's rebuild health over a window.\n\n⛔ READ `observed_s` BEFORE `armed_fraction`. The ledger is per-process and a\nrestart empties it, so a young process honestly reports 0.0 for the seconds it\nsaw. An alert that does not gate on `observed_s` is alerting on an empty\nsample. `rebuild_failing` alone is an instant: it can read `false` on poll\nafter poll on a day with many failed rebuilds."},"PoolsRebuildHealthPair":{"properties":{"archive":{"$ref":"#/components/schemas/PoolsRebuildHealth"},"window":{"$ref":"#/components/schemas/PoolsRebuildHealth"}},"type":"object","required":["archive","window"],"title":"PoolsRebuildHealthPair","description":"`rebuild_health` — the same measurement for each of the two cache keys."},"PoolsWindowFreshness":{"properties":{"age_s":{"type":"number","title":"Age S"},"stale_fallback":{"type":"boolean","title":"Stale Fallback"},"ttl_s":{"type":"number","title":"Ttl S"},"keyed_on":{"type":"string","title":"Keyed On"},"rebuild_failing":{"type":"boolean","title":"Rebuild Failing"},"rebuilding":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Rebuilding","description":"True when this half was served from its last good build because a rebuild of it is running in the background. `stale_fallback` is then true as well and `age_s` is the age of the build that was served. A rebuild that FAILED is `rebuild_failing`, not this. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["age_s","stale_fallback","ttl_s","keyed_on","rebuild_failing"],"title":"PoolsWindowFreshness","description":"`freshness.window` — the half carrying the swap tape (volume, prices, fees).\n\n⚠️ ITS PREDICATE IS AN AGE, because a wall-clock answer has no stamp to\ncompare. A trailing 24-hour window CAN change with no block landing, which is\nwhy this half is not keyed on the archive."},"RateResult":{"properties":{"pair":{"type":"string","title":"Pair"},"unit":{"type":"string","title":"Unit"},"method":{"type":"string","title":"Method"},"why_dispenses":{"type":"string","title":"Why Dispenses"},"samples":{"type":"integer","title":"Samples"},"samples_target":{"type":"integer","title":"Samples Target"},"samples_floor":{"type":"integer","title":"Samples Floor"},"rate":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Rate"},"spread_low":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spread Low"},"spread_high":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spread High"},"spread_ratio":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Spread Ratio"},"from_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"From Block"},"to_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"To Block"},"newest_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Newest At"},"blocks_since_last":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Blocks Since Last"},"below_target":{"type":"boolean","title":"Below Target"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["pair","unit","method","why_dispenses","samples","samples_target","samples_floor","rate","spread_low","spread_high","spread_ratio","from_block","to_block","newest_at","blocks_since_last","below_target"],"title":"RateResult","description":"The rate `/api/rates` publishes.\n\n⚠️ `reason` IS ABSENT ENTIRELY on the publishable branch — it is present only\nwhen `samples` is below `samples_floor`. `spread_ratio` and `newest_at` can\nalso be null on the SUCCESS branch, not only on the refusal branch."},"RatesEnvelope":{"properties":{"result":{"$ref":"#/components/schemas/RateResult"},"as_of_block":{"type":"integer","title":"As Of Block"},"source":{"type":"string","title":"Source"}},"type":"object","required":["result","as_of_block","source"],"title":"RatesEnvelope","description":"GET /api/rates. `result` is an OBJECT, not a list."},"ReorgPreservation":{"properties":{"available":{"type":"boolean","title":"Available"},"since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"},"since_source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since Source"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["available","since","since_source","reason"],"title":"ReorgPreservation"},"ReorgTotals":{"properties":{"forks":{"type":"integer","title":"Forks"},"blocks_removed":{"type":"integer","title":"Blocks Removed"},"blocks_replaced":{"type":"integer","title":"Blocks Replaced"},"blocks_no_canonical":{"type":"integer","title":"Blocks No Canonical"},"blocks_returned_identical":{"type":"integer","title":"Blocks Returned Identical"},"events_removed":{"type":"integer","title":"Events Removed"},"events_truly_orphaned":{"type":"integer","title":"Events Truly Orphaned"},"first_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Seen"},"last_seen":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Seen"}},"type":"object","required":["forks","blocks_removed","blocks_replaced","blocks_no_canonical","blocks_returned_identical","events_removed","events_truly_orphaned","first_seen","last_seen"],"title":"ReorgTotals"},"ReorgsEnvelope":{"properties":{"forks":{"anyOf":[{"items":{"$ref":"#/components/schemas/ForkRow"},"type":"array"},{"type":"null"}],"title":"Forks","description":"NULL means WE COULD NOT LOOK — orphan preservation is not present on this database, so there is no log to read. It is NOT an empty log: `[]` would be a positive claim that this archive has observed no fork. Read `preservation.reason` and `absence_means` before inferring anything about the chain."},"forks_observed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Forks Observed","description":"Distinct forks observed — a LOWER BOUND, see `counting_note`. NULL, not 0, when `forks` is null: zero would be a count of a log that does not exist."},"limit":{"type":"integer","title":"Limit"},"truncated":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Truncated","description":"Whether `forks` was cut at `limit`. NULL when `forks` is null — there was no list to cut."},"totals":{"anyOf":[{"$ref":"#/components/schemas/ReorgTotals"},{"type":"null"}],"description":"Whole-log totals, independent of `limit`. NULL when the log cannot be read; every member would otherwise be a fabricated zero."},"preservation":{"$ref":"#/components/schemas/ReorgPreservation"},"absence_means":{"type":"string","title":"Absence Means"},"counting_note":{"type":"string","title":"Counting Note"},"over_capture_note":{"type":"string","title":"Over Capture Note"},"block_hash_note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Block Hash Note","description":"How to read the block hashes in `forks`. ABSENT — not null — on the not-preserved branch, where there are no forks to describe."},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["forks","forks_observed","limit","truncated","totals","preservation","absence_means","counting_note","over_capture_note","source"],"title":"ReorgsEnvelope","description":"GET /api/reorgs.\n\n⛔ THIS ROUTE HAS TWO 200 BRANCHES:\n  1 · preserved     — orphan preservation is present on this database; every\n                      field below carries a value.\n  2 · NOT preserved — there is no fork log to read. `forks`, `forks_observed`,\n                      `truncated` and `totals` are NULL and `block_hash_note` is\n                      ABSENT ENTIRELY. Still HTTP 200: that is a stable fact,\n                      not a retryable one.\n`[]` and `0` would be POSITIVE CLAIMS ABOUT THE CHAIN over a database on which a\nfork could not have been recorded at all — `null` is the honest answer."},"ResolutionRow":{"properties":{"asset":{"type":"string","title":"Asset"},"tx":{"type":"string","title":"Tx"},"outcome":{"type":"string","title":"Outcome"},"outcome_basis":{"type":"string","title":"Outcome Basis"},"soft_cap_reading_agrees":{"type":"boolean","title":"Soft Cap Reading Agrees"},"xcp":{"type":"number","title":"Xcp"},"minters":{"type":"integer","title":"Minters"},"deadline":{"type":"integer","title":"Deadline"},"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"closed_at":{"type":"string","title":"Closed At"}},"type":"object","required":["asset","tx","outcome","outcome_basis","soft_cap_reading_agrees","xcp","minters","deadline","block_index","event_index","closed_at"],"title":"ResolutionRow"},"ResolutionsEnvelope":{"properties":{"block":{"type":"integer","title":"Block"},"result":{"items":{"$ref":"#/components/schemas/ResolutionRow"},"type":"array","title":"Result"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The ARCHIVE block these rows were built at — the archive head, read once per request and used as the cache key, carried inside the cached value so a request that waited on another's build publishes that build's block. ⛔ NOT `block`: `block` is `ingest_health.chain_tip`, the node tip used as the COUNTDOWN reference, and it is an UPPER bound on this — during catch-up the node tip stands still while the archive advances. Age this answer by `as_of_block`."},"stale_fallback":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Stale Fallback","description":"Always false on this route: it has no fallback arm (a failed rebuild is a 503, never old rows). ⛔ It is NOT `/api/markets/ranking`'s head comparison (`as_of_block != archive_head`): a request that waited on another request's build can publish an `as_of_block` below the head it read and still say false here. Age this answer by `as_of_block`."},"count":{"type":"integer","title":"Count"},"outcome_not_established":{"type":"integer","title":"Outcome Not Established"},"soft_cap_reading_disagreements":{"type":"integer","title":"Soft Cap Reading Disagreements"},"outcome_note":{"type":"string","title":"Outcome Note"}},"type":"object","required":["block","result","count","outcome_not_established","soft_cap_reading_disagreements","outcome_note"],"title":"ResolutionsEnvelope","description":"GET /api/resolutions."},"ResultState":{"type":"string","enum":["complete","truncated","empty_past_end","empty_no_match","empty_source"],"title":"ResultState","description":"⛔ `complete` and `truncated` are the ONLY states that may carry rows; the\nthree `empty_*` states are the ONLY ones that may not. Enforced on `Envelope`,\nboth directions.\n\n⛔⛔ EVERY MEMBER HERE DESCRIBES THE PAGE, NOT THE ANSWER: `result_state`\nanswers \"is there another page\". A response can answer `complete` for its page\nwhile a census beside it is a lower bound; whether an AGGREGATE was computed\nover a complete population is answered by that number's own `Measured.method`.\n\n★ `empty_past_end`: rows matched, but this page is past the last one (an\n`offset` beyond the matching row count). A route emits it only after re-running\nits own filtered query at `offset=0` and seeing a row; if it cannot prove rows\nmatched, it says `empty_no_match` instead."},"RewriteAvailability":{"properties":{"available":{"type":"boolean","title":"Available"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reason"}},"type":"object","required":["available","reason"],"title":"RewriteAvailability"},"SamplerHealth":{"properties":{"last_run_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Run At"},"last_run_as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Run As Of Block"},"last_run_pairs_sampled":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Run Pairs Sampled"},"last_run_dex_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Run Dex Rows"},"last_run_amm_rows":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Run Amm Rows"},"last_run_errors":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Run Errors"},"last_run_had_error":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Last Run Had Error","description":"Whether the run recorded an error. Text NOT republished."},"run_age_s":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Run Age S"},"sampler_alive":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Sampler Alive","description":"run_age_s within max_run_age_s. null = could not determine."},"max_run_age_s":{"type":"number","title":"Max Run Age S"}},"additionalProperties":false,"type":"object","required":["last_run_at","last_run_as_of_block","last_run_pairs_sampled","last_run_dex_rows","last_run_amm_rows","last_run_errors","last_run_had_error","run_age_s","sampler_alive","max_run_age_s"],"title":"SamplerHealth","description":"The price sampler's OWN liveness. Distinct from archive freshness: the\npoller can be healthy while the sampler is dead, and then this series is stale\nwhile `freshness.stale` is false."},"StatsEnvelope":{"properties":{"events":{"type":"integer","title":"Events"},"first_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"First Block"},"last_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Block"},"complete_blocks":{"type":"integer","title":"Complete Blocks"},"complete_contiguous_from":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Complete Contiguous From"},"complete_contiguous_to":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Complete Contiguous To"},"complete_contiguous":{"type":"integer","title":"Complete Contiguous"},"complete_outside_contiguous":{"type":"integer","title":"Complete Outside Contiguous"},"complete_below_floor_top":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Complete Below Floor Top"},"complete_blocks_note":{"type":"string","title":"Complete Blocks Note"},"exact":{"type":"boolean","title":"Exact"}},"type":"object","required":["events","first_block","last_block","complete_blocks","complete_contiguous_from","complete_contiguous_to","complete_contiguous","complete_outside_contiguous","complete_below_floor_top","complete_blocks_note","exact"],"title":"StatsEnvelope","description":"GET /api/stats.\n\n`complete_below_floor_top` is null in the NORMAL case."},"TradeRow":{"properties":{"block":{"type":"integer","title":"Block"},"block_index":{"type":"integer","title":"Block Index"},"event_index":{"type":"integer","title":"Event Index"},"match_id":{"type":"string","title":"Match Id"},"maker_tx":{"type":"string","title":"Maker Tx"},"taker_tx":{"type":"string","title":"Taker Tx"},"maker_address":{"type":"string","title":"Maker Address"},"taker_address":{"type":"string","title":"Taker Address"},"taker_received_asset":{"type":"string","title":"Taker Received Asset"},"taker_received_quantity":{"type":"number","title":"Taker Received Quantity"},"taker_received_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Taker Received Divisible"},"taker_paid_asset":{"type":"string","title":"Taker Paid Asset"},"taker_paid_quantity":{"type":"number","title":"Taker Paid Quantity"},"taker_paid_divisible":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Taker Paid Divisible"},"fee_paid":{"type":"number","title":"Fee Paid"},"final_status":{"type":"string","title":"Final Status"},"status_at_match":{"type":"string","title":"Status At Match"},"taker_received_raw":{"type":"number","title":"Taker Received Raw"},"taker_paid_raw":{"type":"number","title":"Taker Paid Raw"},"traded_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Traded At"}},"type":"object","required":["block","block_index","event_index","match_id","maker_tx","taker_tx","maker_address","taker_address","taker_received_asset","taker_received_quantity","taker_received_divisible","taker_paid_asset","taker_paid_quantity","taker_paid_divisible","fee_paid","final_status","status_at_match","taker_received_raw","taker_paid_raw","traded_at"],"title":"TradeRow","description":"One matched trade. Quantities are never null (a missing value reads 0); the\n`*_divisible` flags are passed through raw and CAN be null; `traded_at` is null\nwhen the block's time is not known."},"TradesEnvelope":{"properties":{"result":{"items":{"$ref":"#/components/schemas/TradeRow"},"type":"array","title":"Result"},"count":{"type":"integer","title":"Count"},"matching":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Matching"},"has_more":{"type":"boolean","title":"Has More"},"venue":{"type":"string","title":"Venue"},"note":{"type":"string","title":"Note"},"source":{"type":"string","title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["result","count","matching","has_more","venue","note","source"],"title":"TradesEnvelope","description":"GET /api/trades.\n\n★ `matching` is null BY DESIGN, and it does not mean zero: it is null exactly\nwhen the page was truncated and `count_total` was not asked for. Its type\ndiffers per route — see BookEnvelope, where it is always an int."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"VenueGapEnvelope":{"properties":{"contract":{"type":"string","title":"Contract","description":"Contract version. Pin it.","default":"1"},"route":{"type":"string","title":"Route","description":"The route that produced this. Stable identifier."},"rows":{"items":{"$ref":"#/components/schemas/VenueGapPoint"},"type":"array","title":"Rows"},"row_count":{"type":"integer","title":"Row Count","description":"len(rows). Duplicated so a truncated read is detectable."},"result_state":{"$ref":"#/components/schemas/ResultState"},"complete":{"type":"boolean","title":"Complete","description":"True if and only if result_state == 'complete'. ⛔ IT IS A CLAIM ABOUT THE PAGE, NOT THE ANSWER. It is the PAGING signal and it pairs with `next_offset`: `complete: true` means no page follows this one (`next_offset` is null), and it is what a machine branches on to stop paging. ⚠️ IT DOES NOT CERTIFY THAT EVERY NUMBER BESIDE IT WAS COMPUTED OVER A COMPLETE POPULATION. A route may hit an internal census cap and still answer `complete: true` for the page — census truncation is carried SEPARATELY, on each affected `Measured.method` string (and in `caveats`), never by flipping this boolean. The live case is `/api/orders/outcomes`' status census. ⇒ To learn whether you have every ROW, read this. To learn whether an AGGREGATE is a count or a lower bound, read that number's own `method`. Refusing COMPLETE at a census cap would need a sixth ResultState member or a new boolean, i.e. a contract break; see this enum's own note."},"limit":{"type":"integer","title":"Limit"},"offset":{"type":"integer","title":"Offset"},"next_offset":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Next Offset","description":"Non-null if and only if result_state == 'truncated'."},"coverage":{"$ref":"#/components/schemas/Coverage"},"freshness":{"$ref":"#/components/schemas/Freshness"},"caveats":{"items":{"type":"string"},"type":"array","title":"Caveats","description":"Known ways this answer misleads if read naively."},"sampler":{"$ref":"#/components/schemas/SamplerHealth"}},"additionalProperties":false,"type":"object","required":["route","rows","row_count","result_state","complete","limit","offset","next_offset","coverage","freshness","caveats","sampler"],"title":"VenueGapEnvelope"},"VenueGapPoint":{"properties":{"sampled_at":{"type":"string","format":"date-time","title":"Sampled At"},"base_asset":{"type":"string","title":"Base Asset"},"quote_asset":{"type":"string","title":"Quote Asset"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this sample was answered against."},"dex_trade_price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Dex Trade Price","description":"Quote per base from the LAST settled match IN THIS WINDOW."},"dex_trades_in_window":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dex Trades In Window","description":"0 is a real observation: we looked and nothing settled."},"dex_absent_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Dex Absent Reason"},"dex_last_trade_price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Dex Last Trade Price","description":"⚠️ A FOSSIL. The most recent settled match at ANY age."},"dex_last_trade_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dex Last Trade Block"},"dex_last_trade_age_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Dex Last Trade Age Blocks"},"amm_spot_price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amm Spot Price","description":"Marginal price from the reserves."},"amm_reserve_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amm Reserve Block"},"amm_reserve_age_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amm Reserve Age Blocks"},"amm_spot_absent_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Amm Spot Absent Reason"},"amm_exec_price":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Amm Exec Price","description":"Realised execution price IN THIS WINDOW."},"amm_swaps_in_window":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Amm Swaps In Window"},"amm_exec_absent_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Amm Exec Absent Reason"},"gap_ratio":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gap Ratio","description":"amm_spot_price / dex_trade_price. BOTH IN-WINDOW. Null unless both exist."},"gap_ratio_absent_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gap Ratio Absent Reason","description":"Why there is no honest gap. Exactly one of this and gap_ratio is set."},"gap_ratio_vs_fossil":{"anyOf":[{"type":"string","pattern":"^(?!^[-+.]*$)[+-]?0*\\d*\\.?\\d*$"},{"type":"null"}],"title":"Gap Ratio Vs Fossil","description":"⚠️ amm_spot_price / dex_LAST_trade_price. Compares a live number to an OLD one."},"gap_ratio_vs_fossil_dex_age_blocks":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Gap Ratio Vs Fossil Dex Age Blocks","description":"How old the DEX side of gap_ratio_vs_fossil is. Required whenever it is set."}},"additionalProperties":false,"type":"object","required":["sampled_at","base_asset","quote_asset","as_of_block","dex_trade_price","dex_trades_in_window","dex_absent_reason","dex_last_trade_price","dex_last_trade_block","dex_last_trade_age_blocks","amm_spot_price","amm_reserve_block","amm_reserve_age_blocks","amm_spot_absent_reason","amm_exec_price","amm_swaps_in_window","amm_exec_absent_reason","gap_ratio","gap_ratio_absent_reason","gap_ratio_vs_fossil","gap_ratio_vs_fossil_dex_age_blocks"],"title":"VenueGapPoint","description":"One sample, one pair, both venues side by side. ⛔ NEVER SUMMED OR AVERAGED\nACROSS VENUES: the DEX and the AMM are separate venues with separate prices."},"VerifyEnvelope":{"properties":{"tx_hash":{"type":"string","title":"Tx Hash"},"found":{"type":"boolean","title":"Found"},"events":{"items":{"$ref":"#/components/schemas/EventRow"},"type":"array","title":"Events"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Note"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"as_of_block":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"As Of Block","description":"The archive head this answer was computed against — max(block_index) FROM blocks WHERE complete. This route publishes it at the envelope level, so 'we have never seen one' and 'our archive stopped four hours ago' are never the same bytes on the wire. Declared optional in this schema; the handler always emits it."}},"type":"object","required":["tx_hash","found","events"],"title":"VerifyEnvelope","description":"GET /api/verify/{tx_hash}.\n\n⚠️ TWO DIFFERENT KEY SETS ON THE 200 PATH:\n    not found -> {tx_hash, found:false, events: [], note}      -- no `source`\n    found     -> {tx_hash, found:true,  events: [...], source} -- no `note`\nThey are mutually exclusive and discriminated by `found`, so `note` and `source`\nare both optional: test whether the key is present, not whether it is null."},"ErrorDetail":{"additionalProperties":false,"description":"FastAPI's own `HTTPException` body — `{\"detail\": \"...\"}` and nothing else.\n\nThis is what a caller receives when the DATABASE could not answer: no database\nconnection is available, or the query ran out of time. The body names the kind\nof failure (`database error: …`), never its message, so no query text and no\nserver detail reaches the wire. A query that ran out of time is RETRYABLE,\nideally with a narrower block window: it is a timeout, not a malformed request,\nand the same request can succeed on a retry. It says nothing about the archive\nbeing wrong, only that this particular question took too long to answer.","properties":{"detail":{"description":"Why the request could not be answered, in words. For a database failure the form is `database error: <kind of failure>`; a query that ran out of time is RETRYABLE, ideally with a narrower block window.","title":"Detail","type":"string"}},"required":["detail"],"title":"ErrorDetail","type":"object"},"PaymentOffer":{"additionalProperties":true,"description":"HTTP 402 — the offer IS the discovery. Emitted by middleware; no handler ran.\n\n⛔ THE OFFER IS ALSO IN A HEADER, AND THE HEADER IS THE NORMATIVE ONE.\n`PAYMENT-REQUIRED` carries the same object base64-encoded, because x402 v2\nsays the offer travels in the header and calls response bodies \"a server\nimplementation concern\". This body is emitted anyway — real sellers do, and a\nv1-era client reads only the body — and both are rendered from ONE method so\nthey cannot disagree about what is being sold.\n\n⚠️ Additional keys are allowed, for the reason `X402Accepts` gives: the\nprimary rail owns this top level, and a different rail would legitimately\nproduce a different envelope.","properties":{"x402Version":{"description":"2. ⚠️ Was 1; the live ecosystem is v2 and several key names moved with it (`maxAmountRequired` → `amount`). Branch on this before reading anything else.","title":"X402Version","type":"integer"},"error":{"description":"`payment required`. Prose — branch on the status code and on `accepts`, never on this string.","title":"Error","type":"string"},"resource":{"$ref":"#/components/schemas/X402ResourceInfo"},"accepts":{"description":"Every way this server will take the money, in preference order. ⛔ An EMPTY list cannot reach you: a request with no usable rail is refused with a 503 rather than offered at a price nobody can pay.","items":{"$ref":"#/components/schemas/X402Accepts"},"title":"Accepts","type":"array"},"payment_rejected":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"⭐ PRESENT ONLY WHEN YOU ALREADY TRIED TO PAY AND IT DID NOT STAND — every candidate's reason, joined by ` · `. Its presence is the difference between 'nobody bought' and 'a buyer exists and cannot get through', and this archive keeps those apart deliberately. ⚠️ It is added AFTER the rail renders the envelope, so it survives the translation while the neutral body's other keys do not.","title":"Payment Rejected"}},"required":["x402Version","error","resource","accepts"],"title":"PaymentOffer","type":"object"},"PaymentRefusal":{"additionalProperties":false,"description":"The payment gate refusing to sell — 503, emitted by middleware, no handler run.\n\n⛔ IT IS A REFUSAL TO **SELL**, NOT A REFUSAL OF THE CALLER, and that is why the\ncode is 503 and never 402: a 402 here would invite payment for an answer this\nservice already believes it should not take money for. A settlement rail's\ncancel-on-error guard keys on HTTP STATUS, so this code is load-bearing.\n⚠️ REACHABLE ONLY WHILE PAYMENT ENFORCEMENT IS ON. With it off the gate is a\npass-through and this shape cannot occur — so a consumer that has never seen it\nhas not proved it cannot arrive.","properties":{"error":{"description":"The refusal in one line, e.g. 'payment required but this server cannot take it'. Prose, and it may be reworded — branch on `reason` instead.","title":"Error","type":"string"},"detail":{"description":"Which decision or wiring is missing, at length. Written for a human reading the body, not for a matcher.","title":"Detail","type":"string"},"source":{"description":"Constant provenance marker carried by every refusal this gate emits: `opreturn.art — check us instead of trusting us`.","title":"Source","type":"string"},"reason":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"⭐ THE MACHINE-READABLE ONE, AND THE ONLY KEY SAFE TO BRANCH ON. The six branches: `no_rail_registered` · `freshness_unknown` · `stale_data` · `params_not_supplied` · `not_in_catalog` · `unresolved_resource`. ABSENT on the unresolved_resource and not_in_catalog bodies, which is why it is optional here — treat a missing `reason` as 'unclassified refusal', never as a default.","title":"Reason"},"resource":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The route template the gate was pricing, e.g. `/api/orders`. ABSENT when route resolution itself failed, which is precisely the case where it would have been most useful.","title":"Resource"},"rails_unusable":{"anyOf":[{"additionalProperties":{"type":"string"},"type":"object"},{"type":"null"}],"default":null,"description":"Rail name -> why that rail cannot take a payment right now. Present ONLY on the `no_rail_registered` branch; an empty mapping and an absent key are different claims and neither means 'a rail is usable'.","title":"Rails Unusable"}},"required":["error","detail","source"],"title":"PaymentRefusal","type":"object"},"X402Accepts":{"additionalProperties":true,"description":"One way to pay, as x402 v2 spells it.\n\n⛔ ADDITIONAL KEYS ARE ALLOWED, ON PURPOSE. This block is rendered by the payment\nRAIL, and a rail may add a field the spec later requires; declaring the key set\nclosed would promise something this design refuses to promise.","properties":{"scheme":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"`exact` on this rail.","title":"Scheme"},"network":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"⚠️ A **CAIP-2** identifier, e.g. `eip155:8453`. The v1 spellings (`base`, `base-sepolia`) are REFUSED at configuration time — a v2 offer carrying one is a mismatch the facilitator rejects only AFTER the buyer has signed.","title":"Network"},"amount":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"⛔ A **STRING**, in the asset's own smallest unit — not a number and not micro-USD. `extra.amount_micro_usd` carries the price this archive actually quoted; `extra.asset_decimals` is what relates the two.","title":"Amount"},"asset":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The token contract to pay in.","title":"Asset"},"payTo":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The payee address. Absent means unconfigured, and an unconfigured rail emits a 503, not this.","title":"Payto"},"resource":{"anyOf":[{"$ref":"#/components/schemas/X402ResourceInfo"},{"type":"null"}],"default":null,"description":"The SAME object as the top-level `resource`, repeated per accept-block because the x402 spec puts it in both places. Optional because it is the RAIL that renders this block: a rail that omits it is legal here and the top-level copy is the one to read."},"maxTimeoutSeconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"How long the facilitator may take. It is not a lifetime for the offer, and this archive publishes none: the window that binds is the signed authorization's own `validBefore`.","title":"Maxtimeoutseconds"},"extra":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"The rail's own diagnostics, and the ONLY place this archive's own vocabulary survives the translation: `amount_micro_usd`, `asset_decimals`, `binding_currency`, `binding_unit`, `binding_quantity`.","title":"Extra"},"extensions":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"default":null,"description":"x402's own forward-compatibility slot. Empty today on every offer this archive emits; absent entirely if a future rail does not render it, which is why it is optional rather than declared as an always-present empty object.","title":"Extensions"}},"title":"X402Accepts","type":"object"},"X402ResourceInfo":{"additionalProperties":true,"description":"What is being sold, in x402's own words. Appears TWICE on purpose.\n\n⚠️ Once at the top level of the 402 and once inside each `accepts` entry — the\nx402 spec's shape in both directions. A body disagreeing with the\n`PAYMENT-REQUIRED` header about what is being sold would be its own bug, so both\nare rendered from one method.","properties":{"url":{"description":"The route template being priced, e.g. `/api/events`.","title":"Url","type":"string"},"description":{"description":"Human-readable, e.g. 'one request — $0.01'. Prose; do not parse a price out of it — read `accepts[].amount`.","title":"Description","type":"string"},"mimeType":{"description":"What you get for the money. `application/json` on every route this archive sells.","title":"Mimetype","type":"string"}},"required":["url","description","mimeType"],"title":"X402ResourceInfo","type":"object"}}},"servers":[{"url":"https://opreturn.art","description":"the public deployment. Every path below is relative to it; this block exists so the map states its own base URL instead of leaving a client to guess one."}]}