Skip to main content

Docs

API guide

This reference documents ReserveBeat's public read API: which endpoints are read without a key and which need one, the seven GET endpoints that serve board coverage, token detail, attestation reports, revisions, and supply history by range and grain, the request limits actually enforced, and how the service answers when a figure is withheld rather than serving an unreviewed number.

Reviewed

Choose a starting point

This reference covers the public read API served under the /v1 prefix. It is a read-only interface over the same records the public pages render, answering in JSON except for the evidence bundle, which is a zip archive: nothing here writes, and nothing here returns a figure the board itself would not show.

Authentication

The current-state endpoints — the board overview, one token, coverage, the attestation inventory, and a single parsed report — are read with no credential at all. Depth over time carries a key in the X-API-Key header. A key is issued from the dashboard on a free account, shown in full exactly once at issue time, and stored afterwards only as a one-way hash beside its prefix, so a lost key is replaced rather than recovered. A key can be limited when it is issued or at any time after, from the dashboard or with POST /account/keys/{key_id}/limits: to some of the /v1 route families its scopes name — account (the balance read), board (every board read, the evidence bundle included) and pricing (the price list) — and to a monthly cap of its own in compute units. A key with no scopes may call every family. A key called outside its scopes is refused with 403 key_scope, and one whose month would pass its cap with 429 key_cap_reached; neither refusal is charged. POST /account/keys/{key_id}/rotate issues a replacement with the same label, scopes and cap, shown in full once, and the old key keeps working for a grace period its holder picks, up to seven days and one day when none is given, so an agent can be moved to the new key before the old one stops. Changing or resetting an account's password ends every account session signed in before it, on every device; a change made while signed in keeps only the session that made it, and API keys are not affected, so revoke any key you do not recognise. An account session lasts 12 hours and is never extended; POST /account/logout ends the session that sends it, and POST /account/sessions/revoke ends every session on the account, the sending one included, while API keys keep working. A key that is presented is verified wherever it is presented: a revoked or malformed one is refused even on an endpoint that would have answered the same request carrying no key, because a revocation the holder cannot observe is not one. An Authorization header carrying a bearer is read as an account session token on every path, /v1 included, so a bearer value that is not a valid session token is refused with 401 even on an endpoint that needs no credential; an API key goes in X-API-Key, and one sent as a bearer is refused with a code that says so. A request for depth carrying no key is refused before any data is read. An X-API-Key header that is present but empty carries no key and is read as none: the request is served on the keyless tier rather than refused for a credential it did not actually offer. A request under /account that needs a session and carries none is refused as unauthenticated before anything it sent is read, whatever its body holds. A signed-in account can close itself with POST /account/close, re-proving its current password: every key and webhook endpoint on it is revoked at once, every later request carrying one of its sessions is refused as unauthenticated, and the answer names what survives the close.

A request carrying a key, read from the environment so the key never lands in a shell history:

curl -s https://reservebeat.com/v1/account/balance \
  -H "X-API-Key: $RESERVEBEAT_API_KEY"

An agent can also get a key from its person without the person copying one. POST /account/agent/start takes no credential and a JSON body with agent_name, the name the agent gives itself, and optionally scopes and cu_cap_month, what it asks for. Its answer carries device_code, the agent's own secret for polling; user_code, the short code its person enters; verification_uri, the dashboard page where they enter it; verification_uri_complete, the same page with the code filled in; expires_in, the seconds the request lasts; and interval, the seconds to wait between polls. Starts are rate limited per network address and refused past the limit with 429 rate_limited. On that page the person signs in, sees the stated name, labelled as stated, and what the agent asked for, chooses the key's route families and its monthly cap in compute units, which every key connected this way carries, and approves or denies; approving needs a confirmed address and the current terms, as issuing a key does. The agent polls POST /account/agent/token with its device_code and no credential. Until the person decides, a poll is refused with 400 authorization_pending; a poll sooner than the interval with 400 slow_down, and the interval is then five seconds longer for every later poll; a denial, or an approval the account can no longer take a key for, with 400 access_denied; and a device code that is unknown, expired or already used with 400 expired_token. After an approval one poll answers 200 with key, key_id, key_prefix, label, scopes, cu_cap_month and expires_at, the key in full exactly once: every later poll answers expired_token, so the agent keeps the key the moment it arrives. The key is an ordinary key of the person's account, listed on the dashboard under the agent's stated name and rotated or revoked like any other.

The same flow as an agent runs it, in a shell with curl and jq and in Python with its standard library alone. Each prints the page address and the code for its person, never the key, and leaves the key in the environment variable the request above reads:

START=$(curl -s -X POST https://reservebeat.com/account/agent/start \
  -H 'Content-Type: application/json' \
  -d '{"agent_name":"my-agent"}')
printf '%s' "$START" | jq -r 'if .error then .error.code else .verification_uri_complete, .user_code end'
DEVICE_CODE=$(printf '%s' "$START" | jq -r .device_code)
INTERVAL=$(printf '%s' "$START" | jq -r .interval)
while sleep "$INTERVAL"; do
  ANSWER=$(curl -s -X POST https://reservebeat.com/account/agent/token \
    -H 'Content-Type: application/json' \
    -d "{\"device_code\": \"$DEVICE_CODE\"}")
  case $(printf '%s' "$ANSWER" | jq -r 'if .key then "key" else .error.code end') in
    authorization_pending) ;;
    slow_down) INTERVAL=$((INTERVAL + 5)) ;;
    key) export RESERVEBEAT_API_KEY=$(printf '%s' "$ANSWER" | jq -r .key); break ;;
    *) printf '%s' "$ANSWER" | jq -r .error.code; break ;;
  esac
done
import json, os, time, urllib.error, urllib.request

ORIGIN = "https://reservebeat.com"

def post(path, body):
    req = urllib.request.Request(ORIGIN + path, data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    try:
        with urllib.request.urlopen(req) as res:
            return json.load(res)
    except urllib.error.HTTPError as err:
        return json.load(err)

start = post("/account/agent/start", {"agent_name":"my-agent"})
if "error" in start:
    raise SystemExit(start["error"]["code"])
print(start["verification_uri_complete"], flush=True)
print(start["user_code"], flush=True)
interval = start["interval"]
while True:
    time.sleep(interval)
    answer = post("/account/agent/token", {"device_code": start["device_code"]})
    if "key" in answer:
        os.environ["RESERVEBEAT_API_KEY"] = answer["key"]
        break
    code = answer["error"]["code"]
    if code == "slow_down":
        interval += 5
    elif code != "authorization_pending":
        print(code)
        break

Endpoints

Ten endpoints, all GET. The seven listed first read the board and return JSON; after them come two about the service itself, also in JSON, and the evidence bundle, which returns a zip archive. Each path is listed below with what it serves.

The same list is published as a machine-readable contract at https://reservebeat.com/v1/openapi.json, an OpenAPI document generated from the running service's own schema and served without a key. It carries the endpoints below and the account endpoints a key is issued from, each marked with whether it needs one, so a generated client knows which calls to sign before it makes them.

Endpoints
EndpointAccessWhat it serves
GET /v1/board/overviewNo keyThe board at a glance: every tracked token with its latest attested coverage, its latest on-chain supply measurement, and its attestation status, together with the review state and the latest anchor covering the release.
GET /v1/board/tokens/{symbol}?chain=No keyOne token in full: its supply series, its attested coverage series, the reconciliations between them, the latest parsed report, and the disclosure-score dimensions. A token tracked on more than one chain is served rather than refused: the response carries that token's home chain and names, in chains_ingested, every chain this board measures and publishes it on (chains carries the same list under its first name, for one release), and an optional chain parameter selects one of those chains instead of the home one. It is a snapshot view: each list carries the most recent entries, and a shortened list says so beside it, with a truncated flag and a count of how many are available. The depth lives elsewhere — the series in the history endpoint, which needs a key and is metered in compute units, and each report in full at the per-period attestation endpoint, free at any age.
GET /v1/board/coverageNo keyWhat the board holds for each token, and how deep and fresh it is: whether a snapshot is enabled, whether an attestation series exists, the expected reporting cadence and where that expectation comes from, publication and capture lag, when the next report is expected and whether it is overdue, and which fields were found in the document rather than assumed.
GET /v1/board/history?symbol=&from=&to=&granularity=&days=API keyCoverage and supply over time, at any depth for any key, metered in compute units by the request's shape. Optional from and to dates bound the range and an optional granularity of hour, day or month picks the last measurement in each bucket, counted in UTC; days is shorthand for a from that many days back, and sending both forms is refused rather than guessed at. Naming one token with symbol serves that token alone and adds its measured on-chain supply series beside its coverage series; with no symbol the response is the coverage table for every tracked token and carries no supply series at all. Every point a series serves lies inside the range; when none does, the newest earlier point is served on its own as latest_outside_range, and a named token's supply series states how many time buckets the range expected, how many hold a reading, and where the gaps are. When the range runs to the present and the bucket in progress holds no reading yet, the gap run reaching it is marked pending and counted in pending_points, so a client alerting on gaps can subtract it rather than raise an alarm early in every bucket. The shape is one of three: every token at once, one token inside the public thirty-day window, or one token reaching past it. Inside the thirty-day window the coverage series is expected to be empty between attestations: an attested coverage point exists only at a report's as-of date, and the newest one usually lies outside that window, so a named token's measured supply series, which gains a reading every hour, is the cheapest signal.
GET /v1/board/attestationsNo keyThe inventory of parsed attestation reports, newest period first per token, with the scope of each report and whether it supersedes an earlier parse. Change notifications are pushed only to an account holding a compute-unit balance or on Enterprise, so without one this index is the change feed: poll it with the ETag it last returned, and a newly landed report appears as a new entry with its own report_artifact_id, while a corrected one keeps its period and comes back with a new report_artifact_id and restated set to true.
GET /v1/board/attestations/{symbol}/{period}?chain=No keyA single parsed report by token and period: the reserve composition as stated, the report's as-of dates, and how many position rows the source document held beyond what is rendered.
GET /v1/board/revisions?symbol=No keyEvery correction ReserveBeat has recorded, newest first, as a supersession: the replaced record beside the record replacing it, both of their timestamps and anchors, and the reason given. It is the log the website's /revisions page shows. Pass symbol to read one token's record. It is free and unmetered: it needs no key, and a key sent with it is verified, recorded as unmetered and never charged.

Beside the board read endpoints, the API answers two questions about itself, also by GET and in JSON: what it charges, and where your key stands.

Pricing and balance endpoints
EndpointAccessWhat it serves
GET /v1/pricingNo keyThe price list the meter enforces, with no key: the compute units each shape of history request costs, the rolling window those shapes are cut at, the free monthly allowance, the pay-as-you-go rate, the throughput budget, the licence each kind of row is served under, and where to buy more. Its figures are read from the same constants the service charges by. It costs nothing. A key sent with it is verified as it is anywhere else under /v1, so a malformed, unknown, revoked or expired key is refused with 401, and a verified key is neither recorded nor charged.
GET /v1/account/balanceAPI keyWhere your key's account stands, read with that key and no login: its prepaid balance in compute units and when the earliest of it expires, what is left of this month's free allowance and when the allowance resets, and the purchase and price-list links. It states the same figures as the account usage summary and the balance-exhausted refusal. A balance read is recorded but costs no compute units and takes nothing from the throughput budget.

And one read that is not JSON: the evidence bundle, the file the verification kit checks.

Evidence bundle endpoint
EndpointAccessWhat it serves
GET /v1/board/evidence/{symbol}/bundleNo keyOne published token's evidence bundle, as a zip archive, with no key: every record of the public registry's evidence chain from genesis, every anchor, the manifests the anchors name and the storage proof behind the token's newest proven snapshot, with third-party documents in it by SHA-256 only. Run rb_verify.py bundle on it. It costs no compute units; a key sent with it is verified as it is anywhere else under /v1, so a malformed, unknown, revoked or expired key is refused with 401, and a verified key is neither recorded nor charged. Its draw on the network-address ceiling is weighted by its size, as the limits below state.

Limits

The current-state endpoints need no key and carry no quota or rate-limit headers, because no account's allowance or budget applies to them for either to describe; the one ceiling that does, on the network address a request comes from, is stated below. A key presented to one of them anyway is still verified — a revoked or unknown key is refused exactly as it is on the keyed tier — and the call is recorded against its account, but it is never charged: it takes nothing from the monthly allowance or the burst, the response is the same keyless one an anonymous caller gets, and the account's usage counts it separately, as unmetered. The keyed tier is metered in compute units: 20,000 a month free, at a steady 20 compute units a second with up to 200 in any rolling 10 seconds. Limits are per account, not per key: every key an account issues draws on the same allowance and the same burst, so a second key adds no headroom. Every served history response — the keyed tier's metered endpoint — carries the quota limit, the quota remaining, the seconds until the monthly reset, and the rate-limit state, so a client never has to infer them; the balance read, which enforces no ceiling and costs nothing, states the allowance in its body and carries none of these headers. X-RateLimit-Limit is the burst allowance in compute units — the bucket X-RateLimit-Remaining counts down — and X-RateLimit-Reset is the moment, in UTC epoch seconds, at which that bucket is full again. The same state is stated a second time in the RateLimit-Policy and RateLimit headers of draft-ietf-httpapi-ratelimit-headers-11, as two named policies: burst, whose quota is the bucket and whose window is the seconds an empty bucket takes to refill, so the quota divided by the window is the steady rate; and quota, whose quota is the monthly allowance and whose window is the length of the current calendar month in UTC. A refusal carries what it can state truthfully, and the rule is one line: the two ceilings ride a response when the request presented a key to the keyed tier or presented a key that was refused, the seconds to the reset ride either way because they are a fact about the calendar rather than about the caller, and a remaining figure rides only where the service read it. A rate-limited request is turned away before the monthly count is read, so it returns the ceilings, the reset and the rate-limit remaining, and omits the quota remaining — in both dialects — rather than guessing at it; a request that presented a key and was refused before it could be verified returns the ceilings and the reset and omits both remaining figures. A request under neither of an account's ceilings — one that presented no key, or a verified key on a keyless endpoint — states the reset alone when it is refused: naming a limit nothing is enforcing on the caller would be worse than naming none. A path this API does not route carries no limit state at all, because there is no endpoint whose limits could be stated.

A window on history is a limit on the time series, not on any report. Every parsed attestation report stays readable without a key at any age, at /v1/board/attestations/{symbol}/{period}, and every tracked token's most recent figures stay public; what compute units buy is the series the history endpoint serves, at any depth.

What a request costs is decided by how it ends. Only a successful (2xx) response from an endpoint that needs a key costs compute units, at its shape's weight in the price list below; a 304, every refusal and every server error cost none, and the keyless endpoints cost none at all. The throughput budget is separate from what is charged: a request is admitted against it at its shape's weight before it is served, and does not get that weight back if it comes back as a 304 or is refused because the allowance and the balance cannot cover it, because the budget measures capacity rather than billing. The monthly free allowance is spent first and the prepaid balance after it, and a request is served only if its whole cost fits in what is left of the two.

While an account holds a positive prepaid balance, or is on Enterprise, its throughput budget is raised to 100 compute units a second, with up to 1,000 in any rolling 10 seconds. The monthly free allowance is the same for every account, and an Enterprise account's higher figures are set on the account.

Beside each account's budget, every board read and balance read under /v1, and a price-list read that carries a key, also draws on a ceiling for the network address it comes from, set high enough that several accounts behind one shared address are not locked out. A request with a key to an endpoint that needs one takes its shape's weight from 200 compute units a second, with up to 2,000 in any rolling 10 seconds; every other request takes one from 10 requests a second, with up to 100 at once. An evidence bundle takes one request for each started megabyte (1,000,000 bytes) of the zip it sends, up to the whole burst of 100; a 304 revalidation or a HEAD of it takes one. A request that carries a key takes one unit of the ceiling before the key is read and the rest of its weight once the key is verified, and a request past the ceiling is refused with 429 and Retry-After. Sign-ups are capped at 20 attempts a day (UTC) from one network address; the next attempt is refused with 429 and a Retry-After that runs to midnight UTC. The account endpoints a session token is presented to are limited per account rather than per address: up to 120 calls at once, then one a second, and a call past that is refused with 429 and Retry-After.

Pricing

One endpoint costs compute units today: history, priced by the shape of the request rather than by the size of the answer. The current-state endpoints need no key and cost nothing. GET /v1/pricing serves the same table with no key, read from the constants the meter charges by.

all_tokens10 compute units
History for every token at once, at any window and any grain.
one_token_public_window20 compute units
History for one token, over a window inside the public thirty-day window.
one_token_full_depth50 compute units
History for one token, over a window reaching past the public window, at day or hour grain.

Past the free allowance of 20,000 compute units a month, compute units cost $0.05 per 1,000, drawn from a prepaid balance: no base fee, no tier and no subscription. They are bought in units of 1,000 compute units, at least 40 units at a time, so the smallest purchase is 40,000 compute units for $2.00. Bought compute units lapse twelve months after purchase, and an unspent purchase is refundable within twenty-four hours of buying it. While a card dispute over a purchase is open, the whole balance is held at zero; when it closes the balance returns, less the disputed purchase's compute units if the card holder wins the dispute.

To buy from a refusal: a request that the free allowance and the balance together cannot cover is refused with 402 cu_balance_exhausted, and the refusal's purchase_url is where to buy. Once self-serve purchase is open, that link opens a checkout for the key's own account with no sign-in, for the smallest purchase unless a units query parameter asks for more; it is valid for at least a day, and a lapsed link is refused with a pointer to a fresh one. GET /v1/account/balance returns the same link beside the balance, so a client can buy before it runs out rather than after. Until self-serve purchase opens, the link is the pricing page's API access card while that page is published, and the API page's section on access beyond the free limits otherwise. Bought compute units can be spent as soon as the payment is confirmed, and raise the throughput budget while any are held.

Caching and conditional requests

Every response carries a strong validator in an ETag header: the SHA-256 of the exact bytes the service produced, before any compression, so it moves whenever anything in the response moves — a figure, a vintage, or a sentence of the disclaimer beside them. Send that value back in an If-None-Match header on the next request and an unchanged response comes back as 304 with no body at all, which is the point: the current-state payloads change when an issuer publishes, when an hourly reading lands, and, on overview, when the evidence chain advances, so a client that already holds the answer pays for headers rather than for the whole aggregate again. Where the edge in front of the service compresses a response, it adds a suffix naming the compression to the tag, -gzip or -zstd, so through reservebeat.com the tag is not the hash of the compressed bytes received; send it back exactly as received, with the same Accept-Encoding, and an unchanged response still comes back as 304, while the same tag sent with a different Accept-Encoding is treated as changed and the full response comes back. Responses that state a data vintage also carry a Last-Modified header, which names the newest vintage the response carries — the latest reading's time or report's as-of date — rather than when the response last changed, so a restatement that replaces a report's figures does not move it. If-Modified-Since is not evaluated: the ETag is the validator, and it does move on every change, a restatement included. Keyless responses — including the one a verified key gets from a keyless endpoint, which carries the same caching headers even where its body differs — are marked public and no-cache, which means a shared cache may keep a copy but must revalidate it before reusing it — no reader is ever handed a stale figure, and a copy that is still current costs a revalidation instead of a body. Responses from the keyed tier are marked private and no-store, because they are fetched with a credential and state that account's own allowance. On the keyed tier a 304 is recorded but not charged: it costs no compute units from the monthly allowance, because nothing was served again, while the compute units it was admitted with still come out of the throughput budget, because comparing the validator is real work and that budget measures capacity rather than billing. It carries the same allowance headers as the full response, showing the allowance it left untouched, so a client can read what it has left without asking again. Keyless responses, and the refusals keyless endpoints return, may also be read cross-origin, and expose the ETag and the request id to the script that made the call so it can poll conditionally and quote a request id in a bug report; a keyed-tier response allows no cross-origin read at all, because a page on someone else's origin is not the caller a credential was issued to. Every answer under /account, refusals included, is marked no-store: it can carry a session token, a key shown once, a webhook secret or the account's address, and no cache may keep a copy of any of those.

Errors and refusals

Every refusal carries a status a client can branch on and, under /v1 and /account, a body in two halves. detail is the reason exactly as it has always been sent: one sentence, or, when the request did not match the schema, the framework's list of field-level issues. error is the machine-readable half: code, a stable identifier from the closed list below; message, the reason as one sentence; request_id, equal to the X-Request-ID header; and doc_url, the address of that code's entry on this page. Branch on error.code, never on the wording. A missing, malformed, unknown or revoked key returns 401, and so does a request for depth over time carrying no key at all. An exceeded rate returns 429 with the seconds to wait. A request on an endpoint that needs a key, which this month's free compute units and the account's prepaid compute units together cannot cover, returns 402, and the refusal states the price per 1,000 compute units, the balance left, when the free allowance resets and where to get more. A 403 means a request asked for more history than a window that applies to it allows. No window applies to a key on this API — history reaching past the public window is served in full and costs the full-depth weight in the price list — so a request carrying a key never meets this refusal; the code stays in the list because the site's own keyless history reads share it. Where it is sent, it replaces a quietly shortened series, and the refusal states the window, the depth asked for, what lifts it and how many points lie outside the window. An unknown token returns 404 with a code of its own, so it is never confused with a path this API does not route; a token named on a chain it is not tracked on returns 404 listing the chains it is tracked on; and a symbol tracked on several chains, asked for without a chain, is not a refusal at all: the response carries that symbol's home chain and names every chain it is published on, so a client never has to ask twice. A period that is not a calendar month written YYYY-MM returns 422, and so does a path carrying a character the usage record cannot hold, because a request the service cannot meter honestly is one it refuses. If the rate limiter is unreachable, the service is at capacity, or the pre-publication review no longer covers the served methodology, the request is refused with 503 rather than served without a limit, without a metering record, or with an unreviewed figure; every 503 carries Retry-After, which is a back-off hint, not a prediction of when service returns.

api_key_required
The route needs an API key and the request carried none; send it in the X-API-Key header.
api_key_invalid
The X-API-Key value is malformed, names no key, or does not match its key; check the value, or issue a new key.
api_key_revoked
The key's secret matched, but the key has been revoked; issue a new key from your account. This code is only ever sent to a caller that presented the whole key.
api_key_expired
The key's secret matched, but the key is past the expiry chosen when it was issued; issue a new key from your account. This code is only ever sent to a caller that presented the whole key.
use_x_api_key_header
An API key was sent as an Authorization bearer token; send it in the X-API-Key header instead. Bearer tokens are for the account endpoints.
account_auth_required
The account endpoint needs the bearer token that signing in returns, and the request carried none.
invalid_token
The account bearer token is malformed, expired, not one this service signed, or was issued before the account's password was last changed or reset; sign in again for a new token.
invalid_credentials
Sign-in was refused: the address and password do not match an active account. The response is the same whichever of the two was wrong.
key_scope
The API key is valid, but its holder scoped it to other route families, so this route was refused and nothing was charged. Only the key's holder can widen its scopes, by signing in to the account; a key issued without scopes may call every route.
current_password_incorrect
The current password given to change the password, or to close the account, does not match the account's password; nothing was changed. Use the password reset link instead if it is lost.
email_unverified
The account's email address has not been confirmed yet, so no new API key or webhook endpoint can be created for it. Open the confirmation link mailed at signup, or request a new one; signing in, reading and revoking are unaffected.
terms_required
The currently published terms have not been accepted. Signup must accept them; an account must accept the current version before it can create a new key or webhook endpoint.
terms_version_stale
The terms version stated in the request is not the one published now; read the current terms and state that version, or omit it.
plan_depth_exceeded
The request asked for more history than the window that applies to it allows. No window applies to a request carrying an API key, which is served any depth at the full-depth weight, so this is sent only to a keyless history read. The refusal states the window, the depth asked for, the one plan that lifts it and how many points lie outside the window; ask for a shorter range to be served now, or send a key.
plan_required
The feature requested needs a positive prepaid compute-unit balance or the enterprise plan, and the account has neither.
key_limit_reached
The account already holds the most unrevoked API keys one account may hold; revoke one to issue another.
unknown_token
No token by that symbol is tracked. The token index lists every tracked symbol.
unknown_chain
The token is tracked, but not on the chain named in the request. The refusal lists the chains it is tracked on.
unknown_period
The token is tracked, but no active report is published for that month. The attestation index lists every published report by token and month.
already_revoked
The key or webhook endpoint named is already revoked; nothing was changed.
unknown_user_code
No pending connect request has this code: it may be mistyped, may have expired, may already have been approved or denied, or may already have delivered its key. Each code that matches no pending request counts against the signed-in account's limit on wrong codes, which answers rate_limited once spent.
invalid_period
The period is not a calendar month written YYYY-MM (a four-digit year, a hyphen and a two-digit month), so it could not name a report.
validation_error
The request did not match the published schema; detail lists each offending field and why, without repeating the submitted value.
invalid_parameter
A value in the request was refused by the service itself, for the reason message states: for example a date range that runs backwards, both forms of a range at once, a password that is too short, or a control character in a path.
disposable_email
The sign-up address is at a disposable-email domain, and this service does not open accounts on one; sign up with an address you will keep.
rate_limited
Requests arrived faster than a limit allows: the key's throughput budget, the ceiling on one network address, the sign-in throttle, or the daily cap on sign-ups from one network address; wait the number of seconds in Retry-After, then retry.
quota_exhausted
No longer sent: a spent monthly allowance is now refused with cu_balance_exhausted. The code stays in this list so a client that branches on it keeps working.
cu_balance_exhausted
The month's free compute units and the account's prepaid compute units together cannot cover this request, after what the account's requests still in flight hold, so it was refused and nothing was charged; requests sent together are never served past what is left. The refusal states the price per thousand compute units, the balance left, what is left of the month's allowance, what this request costs, the cheaper request shapes that would still be served, when the free allowance resets and where to buy more. To be served, lower the request to one of those shapes, or add funds by buying compute units. Retry-After, the seconds until the reset, is sent only when what is left cannot cover even the cheapest priced request; while it can, a cheaper request is still served.
key_cap_reached
This API key has a monthly compute-unit cap set by its holder, and this request would take it past that cap, after what the key's requests still in flight hold, so the request was refused and nothing was charged. The refusal states what this request costs, what is left of the cap and the cheaper request shapes that still fit it. The cap counts what the key spent this calendar month (UTC) and resets at the start of the next; only the key's holder can raise it, by signing in to the account. Retry-After, the seconds until the reset, is sent only when what is left of the cap cannot cover even the cheapest priced request.
authorization_pending
The agent's person has not yet approved or denied this connect request; poll again after the interval the start response gave.
slow_down
This connect request was polled sooner than its interval allows, so its interval grew for this and every later poll; Retry-After carries the new interval in seconds.
access_denied
No key was issued for this connect request: the agent's person denied it, or their account could no longer take a new key when the agent came to collect it. Start a new connect request to ask again.
expired_token
The device code is not one this service issued, has expired, or has already delivered its key; start a new connect request.
limiter_unavailable
The rate limiter could not be read, so the request was refused rather than served without a limit. Retry after the seconds in Retry-After, which is a back-off hint, not a prediction of when service returns.
at_capacity
No database connection was available in time, so the request was refused rather than queued. Retry after the seconds in Retry-After, which is a back-off hint, not a prediction of when service returns.
board_withheld
The recorded pre-publication review does not cover the methodology currently served, so the figures are withheld rather than served unreviewed. Retry-After is a back-off hint; the withholding lifts only when a new review is recorded.
mail_unavailable
The request depends on sending an email (a confirmation or password-reset link, or the notice that a password changed) and this service has no outbound mail configured, so nothing was recorded rather than a link that could never arrive. Retry-After is a back-off hint, not a prediction of when mail returns.
mail_budget_exhausted
The request depends on sending an email, and this hour's budget for account mail, which every account shares, is already spent, so nothing was done rather than accepting a request whose mail would not be sent. Retry after the seconds in Retry-After, when the next hour's budget begins.
agent_connect_unavailable
This deployment has no public web address configured to send the agent's person to, so no connect request was recorded. Retry-After is a back-off hint, not a prediction of when connecting returns.
bad_request
The request was refused as malformed; message states why.
unauthorized
The request's credentials were refused; message states why.
forbidden
The request is not permitted for this caller; message states why.
not_found
Nothing is published at this path, or the resource it names does not exist.
method_not_allowed
The path exists but does not accept this HTTP method.
request_timeout
The request did not arrive in full within the time the server waits for it, so it was refused before any route read it and the connection was closed; send it again.
conflict
The request conflicts with the resource's current state; message states how.
gone
The resource named existed but is no longer available.
request_too_large
The request body is larger than this route accepts.
request_header_too_large
The request's headers are larger than the server at this address accepts, so it was refused before any route read it and the connection was closed; send it again with fewer or smaller headers. Behind this address, the API's own server answers only on the same machine, so no request from outside reaches it; a very large header block sent to it there is refused by that server in plain text, without this envelope.
internal_error
The service failed while handling the request; quote request_id when reporting it.
service_unavailable
A part of the service this response depends on could not be read, so the response was withheld rather than served incomplete. Retry after the seconds in Retry-After, which is a back-off hint, not a prediction of when service returns.

When the board withholds

A withheld board is a withheld API. When the pre-publication review no longer covers the served methodology, figure endpoints return 503 with a statement of why, and no figure is served in the meantime. That refusal is the designed behaviour rather than an outage, and the status page reports the current state.

Attestation-derived figures also age out of a response. Once a token's newest report is older than the cadence expected of that issuer plus a grace period, the composition, coverage, reconciliation and disclosure-score fields for that token come back empty and a withheld_stale object takes their place, naming the newest report month on file and the cadence expected. The token's attestation_status reads WITHHELD_STALE, its coverage-history series is served empty, and independently measured supply is unaffected. Nothing is deleted: the figures return with the next report the pipeline parses.

How figures are returned

Figures come back as strings, exactly as the source document states them, and are never recomputed for presentation. A stated amount is written at a fixed scale of six decimal places, so its value is the document's while its digits gain trailing zeros, and a line the document prints as a dash or as a zero amount is returned as zero. A value the source does not state is returned as null and never as zero. The data definitions page gives the exact arithmetic behind every computed metric. Timestamps are ISO 8601 date-times in UTC, marked with a trailing Z, and their precision varies — some carry whole seconds and some a fraction of a second — so read them with an ISO 8601 parser rather than one fixed format.

Status values

A field that can hold only one of a fixed set of values lists that set in the machine-readable contract, so a generated client can branch on it. The most-used sets are below, read from that same contract rather than written out a second time, each with the sentence the contract carries for its field.

attestation_status(BoardCoverageToken)
CURRENT | STALE | WITHHELD_STALE | NONEThe age of a token's most recent report month, measured against the cadence expected for that token — the cadence recorded for its attestation source, or the default cadence when none is recorded. Past that limit the status reads STALE; within it, CURRENT. Past it by a further stated withholding grace, the status reads WITHHELD_STALE: the board stops serving that token's report-derived figures and states the withholding instead, while its on-chain supply still renders. By default the limit is forty-five days after the report month ends for a monthly cadence or when no cadence is recorded, one hundred and six days for a quarterly cadence, one hundred and ninety-eight days for a semiannual cadence and three hundred and eighty days for an annual cadence: each cadence's own period plus the same fourteen days of publication grace. The withholding grace is a further thirty days by default, so the figures are withheld past seventy-five days for a monthly cadence, one hundred and thirty-six days for a quarterly cadence, two hundred and twenty-eight days for a semiannual cadence and four hundred and ten days for an annual cadence. Every token card and token response states its own two limits, as stale_after_days and withheld_after_days. A token with no attestation series at all reads NONE, which is an absence rather than a value. The status states age, not lateness: an issuer publishing on its usual schedule can read STALE while its next report is not yet due, which is what overdue answers.
status(BoardReconciliation)
RECONCILED | WITHIN_BOUND | EXCEEDS_TOLERANCE | NO_CONTEMPORANEOUS_SNAPSHOTThe comparison outcome. RECONCILED (ETHEREUM or NETWORK scope): the supply measured at the stated cutoff is within the tolerance of the attested figure. WITHIN_BOUND (TOTAL scope): the home-network supply measured at the cutoff does not exceed the attested whole-token total plus the tolerance, a bound check rather than a reconciliation. EXCEEDS_TOLERANCE: the comparison does not hold. NO_CONTEMPORANEOUS_SNAPSHOT: nothing was measured at the cutoff (no_verdict_reason says why).
attested_scope(BoardReconciliation)
ETHEREUM | NETWORK | TOTALWhat the report itemized for this comparison: ETHEREUM when it states a per-network liability row naming the token's network and that network is Ethereum (a direct comparison), NETWORK when it states such a row for a token whose home network is another one (the same direct comparison, on that network), TOTAL when it states only a whole-token liability (a bound check of the supply measured on the token's home network against that total).
source_tier(BoardSupplyFigure)
STORAGE_PROVEN | ETH_CALL | RPC_QUORUMThe evidence tier of this reading: storage-proven, a quorum contract call, or RPC-quorum agreement only, from strongest to weakest.
anchor_status(BoardSupplyFigure)
PENDING | ANCHORED | LATE_ANCHOR | UNANCHOREDWhether the snapshot's evidence record is covered by an external timestamp anchor (pending, anchored, anchored late, or not yet anchored); null when there is no evidence record to anchor.
report_scope(BoardReportDetail)
SINGLE_TOKEN | CONSOLIDATED_GROUPWhether the report covers this token alone or a consolidated group of the issuer's tokens.
assurance(BoardReportDetail)
ATTESTED | EXAMINEDWhat stands behind these figures: EXAMINED when the document's engagement wording classifies as an examination or audit, otherwise ATTESTED.
row_kind(BoardCompositionRow)
CATEGORY | POSITION | TOTAL | LIABILITYThe kind of line: the reserve TOTAL, the token LIABILITY, a reserve CATEGORY, or a per-network POSITION of the token liability.
category(BoardCompositionRow)
US_TREASURY | TREASURY_REPO | GOVT_MMF | CASH | TIMING | OTHER | CORPORATE_BOND | PRECIOUS_METALS | BITCOIN | PUBLIC_EQUITY | SECURED_LOAN | OTHER_REPO | NON_US_TREASURY | TOTAL_RESERVE | TOKEN_LIABILITYReserveBeat's normalized category for this line, used to compare reports whose verbatim labels differ.
unit(BoardCompositionRow)
USD | TOKENThe unit the report states this amount in, never converted: USD on every reserve line; on a token-liability line, TOKEN when the report counts tokens and USD when it states the liability in dollars.
outcome(BoardCorroboration)
CORROBORATED | DIVERGENT | NOT_APPLICABLE | WITHHELD_STALEResult of comparing an SEC N-MFP filing's net assets with the matching attested fund figure: corroborated within tolerance, divergent beyond it, not applicable, or withheld because the token's attested figures are withheld as stale.
status(BoardReviewStatus)
CURRENT | STALE | MISSINGWhether the latest board review covers the current methodology: CURRENT if it does, STALE if it does not or cannot be read, MISSING if no review exists.
figure_kind(BoardIssuerStatementFigure)
TOKENS_IN_CIRCULATION | TOTAL_RESERVES | ISSUED | REDEEMED | TOTAL_AUTHORIZED | NOT_ISSUED | QUARANTINEDWhat the figure measures, such as tokens in circulation, total reserves, issuance or redemption flows, or a per-network balance.
field(V1PlanGated)
restatementsThe name of the response field that was not served under the caller's plan; currently only restatements.

Webhooks

Webhook deliveries are signed: every POST carries an X-ReserveBeat-Signature header holding a timestamp and an HMAC-SHA256 over the timestamp and the raw body, under the endpoint's own secret. The webhooks page states the headers, the tolerance, the rotation overlap and the test ping, with verification code in Python and Node and test vectors to check it against.

Webhooks

Versioning and corrections

The interface is versioned in the path. Corrections to already-published records are made by supersession rather than by overwrite: the replacing record is what the API serves, each parsed report states whether it supersedes an earlier one, and the public revision log lists both sides of every correction.

Nothing under /v1 changes in a way that breaks a working client without notice. Added fields and added headers are not breaking and ship without notice. A field removed or retyped, a status code that changes meaning, or an endpoint withdrawn is breaking: it is announced on the changelog at least 90 days before it takes effect, and from the day it is announced every response from an affected endpoint carries a Deprecation header, a Sunset header naming the day it stops, and a Link header pointing at the changelog entry. No endpoint under /v1 is deprecated today. One field is marked deprecated in the contract: chains, the same list as chains_ingested under the name it was first published with, which is still served; no date for its removal has been announced.

An API bug — a response that contradicts this page, or a refusal that looks wrong — is reported to the address published on the contact page. Quote the request id: error.request_id in a refusal body, or the X-Request-ID header every response carries, names that exact request in the service's own record. Contact

Markdown versions

Every page whose full text this site holds is also served as plain Markdown at the same path under /md, with a .md suffix: /md/docs.md for this page, /md/data-definitions.md for the data definitions, /md/learn/attestation-engagement.md for a term, and every term in the glossary the same way. A page held here only as a summary is not mirrored: its /md path is not found, rather than serving part of a page as though it were the whole one. The Markdown carries the page's own text and no figures, so nothing read from it can go stale against a corrected number.

Elsewhere

Terms for reusing the data are on the license page, the rate and the free allowance are on the pricing page, and two machine-readable indexes are published: a short one listing the pages a crawler or model should read first, and a full one that also carries each page's own description.

Questions

Common questions

Do I need an API key?

Not for the current state. The current-state endpoints need no key; history over time needs a key from a free account, sent in the X-API-Key header.

Where are the parameter names defined?

In the machine-readable contract at /v1/openapi.json. Take each endpoint's parameter names from that contract rather than guessing them.

Why does a figure come back as a string?

Because it is returned exactly as the source document states it and is never recomputed. A value the source does not state comes back as null, never as zero.

What does a 503 from a figure endpoint mean?

That the board is withheld: the pre-publication review no longer covers the served methodology, so no figure is served until a new review covers it. It is the designed behaviour, not an outage.