REST API Documentation
Base URL: /api/v1
The API is anonymous by default — no authentication is required for read endpoints. Optionally send an API key to raise your rate limit. CORS is open. All JSON responses are UTF-8.
Rate Limits
Per-IP request limits are enforced per surface. Static assets, /health, /favicon.ico, /robots.txt, and /sitemap.xml are exempt.
- API (
/api/v1/*, incl. the WhatsonChain-compatible mirror): 240 requests per minute per IP. - Interactive site (HTML pages and HTMX partials): 900 requests per minute per IP. A single page view fans out into several requests, so this surface has a higher limit than the API.
- Transaction broadcast (
POST /tx/broadcast,POST /tx/broadcast/multi): a separate, stricter cap of 10 per minute per IP, layered on top of the API limit because these routes proxy to external ARC infrastructure.
Successful responses include:
X-RateLimit-Limit: requests allowed per window.X-RateLimit-Remaining: requests left in the current window.
When you exceed the limit you get HTTP 429 Too Many Requests with body {"error": "rate limit exceeded"}. Wait at least one minute and retry.
Requests carrying a valid API key are metered against their key's tier instead of the per-IP bucket.
Authentication
Authentication is optional. Anonymous requests work within the per-IP limits above; supplying an API key meters your requests against your key's tier and raises your ceiling. Send the key one of two ways:
Authorization: Bearer <key>X-API-Key: <key>
Keys look like bb_live_… (mainnet) or bb_test_… (testnet) and are issued by the operator. An invalid or revoked key returns HTTP 401; omit the header to fall back to anonymous access. Keyed responses include an X-API-Tier header. Tier limits (for this deployment):
| Tier | Requests per minute |
|---|---|
| Anonymous (per IP) | 240 |
free | 600 |
pro | 3000 |
enterprise | 12000 |
/key/usageReturns the calling key's tier, per-minute limit, and today's request total. Requires a key (401 otherwise). Response: {"key_prefix", "tier", "limit_per_min", "used_today", "reset_at"}.
Try it
Send a live request to this API from your browser. Anonymous requests work within the per-IP limits; paste an API key to use your tier's higher limit. The key is used only by your browser for these requests and is never sent anywhere else.
Pagination
Pagination conventions vary by endpoint depending on the underlying data shape. Cursor-style endpoints page through ordered series (blocks, address transactions); offset-style endpoints page through indexed snapshots (rich list, token holders). Where both are offered on the same resource, the cursor form is preferred for deep pages.
| Endpoint | Style | Parameters | Default / Max |
|---|---|---|---|
/blocks | Cursor | from, limit | 20 / 100 |
/address/<addr>/txs | Cursor | from_height, limit | 20 / 100 |
/address/<addr>/utxos | Offset (legacy) or Cursor | page, limit, sort, or cursor | 20 / 100; page capped at 50 |
/stats/richlist | Offset | page, limit, sort, dir | 20 / 100 |
/tokens | Offset | page, limit, sort, dir | 20 / 100 |
/token/<id>/holders | Offset | page, limit | 20 / 100 |
/token/<id>/history | Offset | page, limit | 20 / 100 |
Cursor endpoints page backwards through the chain (for blocks/txs the natural order is newest→oldest). Pass the last item's key (a height) as from / from_height to fetch the next page. Offset endpoints use zero-indexed page.
Sorted UTXO limits. On /address/<addr>/utxos, any sort other than the default height:desc must scan the address's whole UTXO index to select the page, so two bounds apply and both return 400. A page beyond the first 1,000,000 entries of the sort order is rejected; and an address holding more than 5,000,000 UTXOs cannot be sorted at any depth. Both are rare—fewer than 100 addresses on mainnet exceed the second bound, all of them 1‑satoshi dust. The default height:desc sort with cursor pagination is subject to neither bound and reaches every UTXO of any address.
Blocks
/block/tipReturns the current chain tip block.
/block/latestReturns the latest block (alias for tip).
/block/<id>Returns a block by height or hash.
/block/<id>/protocolsReturns the protocol tag distribution for a block. Response: array of {"protocol", "name", "short", "color", "tx_count"} covering detected on-chain protocols (ORD, BSV21, MAP, B, etc.) within the block.
/blocks?from=&limit=Returns a paginated list of blocks. Cursor-style: from is the upper-bound height (exclusive when paging back), limit defaults to 20, max 100.
/block/header/height/<height>/rawReturns the raw 80-byte block header as binary (application/octet-stream) for a given height. Use /block/headers/latest for a hex-encoded variant.
/block/headers/latestReturns the 80-byte block header of the current chain tip as a hex string.
/block/headers/resourcesReturns JSON array of downloadable header chunk URLs (2016-block difficulty periods). Each entry has url and label fields.
/block/headers/<from>-<to>Downloads concatenated raw 80-byte headers for a height range as binary. Max 2016 blocks per request. Useful for SPV header chain sync.
/block/count?minerId=<miner>Returns the total number of blocks mined by a specific miner. Response: {"miner": "TAAL", "blocks": 12345}
Transactions
/tx/<txid>Returns a parsed transaction with inputs, outputs, and spend status. Use the always-present confirmed boolean (or block_height) to test confirmation: confirmations is omitted while a transaction is unconfirmed and only appears once it is mined.
/tx/<txid>/hexReturns the raw transaction as a hex string.
/tx/<txid>/binReturns the raw transaction as binary.
/tx/<txid>/proofReturns the merkle proof for a transaction in TSC format. Optional ?targetType= selects what target carries: merkleRoot (the default) or hash for the block hash. header returns 501 — the 80-byte header cannot be built here. Any other value returns 400.
/tx/<txid>/inscription/<vout>Returns the inscription content embedded in the specified output. Served with the original MIME type (e.g. image/png, text/html). Returns 404 if no inscription exists at that output.
/tx/<txid>/nft/<vout>Renders the NFT at the specified output, resolving the seed-inscription chain (re-inscriptions follow the prior tx's inscription content). Served with the original MIME type. Returns 404 if no inscription is reachable from that output.
/tx/<txid>/bfile/<vout>Returns the B:// file content embedded in the specified output. Served with the original MIME type. Returns 404 if no B:// data exists at that output.
/tx/<txid>/media/<vout>Returns embedded media from the output, auto-detecting the format (inscription, B://, or other on-chain media). Served with the detected MIME type. Returns 404 if no media is found.
/tx/<txid>/beefReturns a BRC-95 Atomic BEEF bundle (transaction + ancestor chain + merkle proofs) as binary. Used for SPV verification.
/tx/<txid>/beef/hexReturns a BRC-95 Atomic BEEF bundle as a hex string.
/tx/<txid>/statusReturns the confirmation status of a transaction.
/tx/<txid>/propagationReturns propagation status across BSV nodes — shows which nodes have seen the transaction and whether it is in mempool or confirmed.
/tx/<txid>/graphReturns the transaction ancestry graph — a tree of input transactions and their ancestors. Useful for visualizing transaction lineage.
Two flags qualify the result. truncated means the node cap was reached and the traversal stopped early; what is present is complete and correct. partial means a backend lookup of the ROOT transaction's spends or input values failed, so the graph was built from incomplete data — outgoing edges may be missing entirely, or input values and fees absent. The request still answers 200, so a client that cares about completeness must check partial rather than read an empty fan-out as fact.
/tx/broadcastBroadcasts a raw transaction. Body: {"rawtx": "hex"}
/tx/broadcast/multiBroadcasts multiple raw transactions. Body: array of hex strings.
Browser origin policy. The broadcast routes (including the WoC-compatible /tx/raw) refuse requests that carry an Origin header from a site other than this one, with 403. This stops a third-party page from having a visitor's browser submit a pre-signed transaction using the visitor's IP and our upstream quota. Server-side clients (curl, backends, scripts) send no Origin header and are unaffected on every accepted content type.
Addresses
/address/<addr>Returns address info with balance and UTXO count.
/address/<addr>/txs?page=Returns paginated transaction history for an address.
/address/<addr>/utxos?page=Returns paginated UTXOs for an address. Confirmed UTXOs only — unconfirmed change outputs still in the mempool are not included. Clients that spend unconfirmed change (e.g. faucets) should use the mempool-aware WoC-compatible endpoint /api/v1/bsv/main/address/<addr>/unconfirmed/unspent (bulk: POST /api/v1/bsv/main/addresses/unconfirmed/unspent).
/address/<addr>/balanceReturns just the balance for an address as a single confirmed balance field. Confirmed only — for a WoC-style {confirmed, unconfirmed} split, use the WoC-compatible /api/v1/bsv/main/address/<addr>/balance (or /confirmed/balance and the mempool-aware /unconfirmed/balance).
/address/<addr>/scriptsReturns the locking scripts associated with an address.
/address/<addr>/tokensReturns BSV-21 token holdings for an address.
/address/<addr>/token/<tokenId>/historyReturns token transfer history for a specific token at an address.
TXO
/txo/<txid>/<vout>Returns a specific transaction output.
/txo/<txid>/<vout>/spendReturns the spending transaction for an output, if spent.
Downloads
/download/tx/<txid>Downloads the raw transaction as a binary file.
/download/tx/<txid>/hexDownloads the raw transaction as a hex text file.
/download/tx/<txid>/output/<index>Downloads a specific output script as binary.
/download/tx/<txid>/output/<index>/hexDownloads a specific output script as hex.
/download/tx/<txid>/input/<index>Downloads a specific input script as binary.
/download/tx/<txid>/input/<index>/hexDownloads a specific input script as hex.
/download/tx/<txid>/receiptDownloads a PDF receipt for a transaction.
/download/address/<addr>/statementDownloads a PDF statement of recent transactions for an address.
Search
/search?q=Auto-detects query type (txid, block hash/height, address) and returns a redirect.
Stats
/stats/summaryReturns chain summary (height, hashrate, difficulty, price, market cap).
/stats/networkReturns daily network statistics (blocks, txs, fees, hashrate).
/stats/miningReturns mining pool distribution.
/stats/mining/histogram?period=<24h|7d|30d>Returns time-bucketed mining data. Bucket sizes: 24h→1h, 7d→6h, 30d→1d. Response: array of {"bucket": "...", "miner": "TAAL", "blocks": 5}
/stats/props/histogram?period=<24h|7d|30d>Returns time-bucketed block property averages (size, tx count, fees). Bucket sizes: 24h→1h, 7d→6h, 30d→1d.
/stats/protocols?days=<7>Returns protocol tag counts from recent transactions. Response: array of {"protocol": "ord", "tx_count": 5000}
/stats/protocols/histogram?period=<24h|7d|30d>Returns time-bucketed protocol tag counts. Bucket sizes: 24h→1h, 7d→6h, 30d→1d. Response: array of {"bucket": "...", "protocol": "ord", "tx_count": 5000}
/protocol/{key}/txs?page=&limit=Returns the paginated transaction list for a single protocol (same data as the /protocol/{key} HTML page), including the daily activity chart series. 404s if key is not a known protocol tag.
/protocol/{key}/history?days=<90>Returns daily tx-count history for a single protocol. days is clamped to 1–365 (default 90). Response: array of {"bucket": "...", "protocol": "ord", "tx_count": 5000}. 404s if key is not a known protocol tag.
/stats/richlist?page=Returns paginated rich list.
/stats/price-historyReturns historical BSV price data.
/stats/chart/<metric>Returns time-series data for a given stat metric. Valid metrics: tx_count, total_fees, avg_fee, avg_fee_rate, avg_tx_size, median_fee, median_tx_size, total_output_value, coin_days_destroyed, hashrate, difficulty, avg_block_size, total_size, avg_tx_rate, miners_revenue, circulating_supply, utxo_count, blockchain_size, total_tx_count, exchange_rate, mining. Supports ?days= query parameter (default 30, max 10000 — clamped to available history from genesis; long windows are downsampled). Note: exchange_rate is limited to 365 days (CoinGecko free tier).
Exchange Rate
/exchangerateReturns the current BSV exchange rate in USD.
/exchangeratesReturns BSV exchange rates in multiple currencies.
Mempool
/mempoolReturns mempool summary (tx count, size, usage, min fee).
/mempool/statsReturns mempool fee rate and size distribution histograms. Response includes tx_count, total_fee_bsv, avg_fee_bsv, fee_rate_buckets (sat/byte ranges), and size_buckets (byte ranges). Cached for 30 seconds.
/mempool/protocolsReturns detected protocol tags in mempool transactions with counts. Each entry has protocol, short, color, and count. Parses every mempool tx for protocol detection (ORD, BSV21, MAP, B, etc.). Cached for 60 seconds.
Mining
/miner/feesReturns current mining fee statistics.
/policyReturns the current fee policy.
Tools
/tools/decodeDecodes a raw transaction. Body: {"rawtx": "hex"}
/tools/script/decode?hex=Decodes a script hex string.
/tools/address/<addr>Converts an address between formats. Returns the base58, scripthash, and script hex for a given address.
/tools/peersReturns the connected node peers (proxies the BSV node's getpeerinfo). Each entry includes addr, services, conntime, version, subver, and traffic counters.
BSV-21 Tokens
/tokens?page=Returns a paginated list of all BSV-21 tokens.
/token/<tokenId>Returns details for a specific token (name, symbol, supply, holder count).
/token/<tokenId>/holders?page=Returns paginated list of token holders with balances.
/token/<tokenId>/history?page=Returns paginated token transfer history.
/address/tokens/balanceMulti-address batch token balances. Body: {"addresses": ["addr1", "addr2", ...]} (max 20 addresses per request). Returns an array keyed per address: {"address", "tokens": [{"token_id", "symbol", "decimals", "balance"}]}. Invalid or failed addresses return a per-item "error" with "tokens": [] rather than failing the whole batch.
/address/tokens/unspentMulti-address batch unspent token outputs. Body: {"addresses": ["addr1", "addr2", ...]} (max 20 addresses per request). Returns an array keyed per address: {"address", "utxos": [{"txid", "vout", "token_id", "symbol", "decimals", "amount", "block_height"}]}. Each address returns up to 100 most-recent unspent token UTXOs. When the returned list is not guaranteed complete — the 100 cap was reached, or (in Pebble mode) the spent-status scan hit its work guard on an address with a very large spent-token history — the entry includes "incomplete": true; treat such a list as partial. Invalid or failed addresses return a per-item "error" with "utxos": [].
Health
/healthReturns "ok" if the server is running. (Not under /api/v1 prefix.)
WebSocket
Connect to /ws for real-time updates. Subscribe by sending subscribe:<channel> (and unsubscribe:<channel>) as a text message. Valid channels:
blocks— new block notifications.mempool— mempool transaction feed.lock:address:<addr>— outputs paying to an address (alsolock:scripthash:<64-hex>).spend:address:<addr>— inputs spending from an address (alsospend:scripthash:<64-hex>).tx:<64-hex txid>— events for a specific transaction (currently:seenwhen it enters the mempool; lowercase hex).
Example: subscribe:lock:address:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa to watch payments to an address.
Webhooks
Get an HMAC-signed HTTP POST when a selector fires, instead of polling or holding a WebSocket connection open. Requires a self-serve account (see /account); the subscription cap depends on your active keys' tier — the free tier includes none.
/account/webhooksRegisters a subscription. Form fields: selector, callback_url. Session-authenticated (dashboard form), not part of the /api/v1 surface.
Selectors
Same grammar as the WebSocket channels below, EXCLUDING the bare blocks/mempool feeds — those stay public, no-auth, WebSocket-only:
lock:address:<addr>/lock:scripthash:<64-hex>— outputs paying to an address.spend:address:<addr>/spend:scripthash:<64-hex>— inputs spending from an address.tx:<64-hex txid>— events for a specific transaction (currently:seen).
Callback URL requirements
Must be https://. Every IP the hostname resolves to must be a public, globally-routable address — loopback, private (RFC1918), link-local (including the 169.254.169.254 cloud-metadata address), and unspecified addresses are all rejected, both at registration time and again at delivery time. The delivery request never follows redirects.
Signature verification
Each delivery carries:
X-Webhook-Signature: sha256=<hex HMAC-SHA256 of the raw request body, keyed by your subscription's secret>X-Webhook-Event: <selector>Content-Type: application/json
The secret is generated server-side and shown exactly once when you create the subscription — store it; it cannot be recovered, only rotated by deleting and re-creating the subscription. Recompute the HMAC over the raw body and compare using a constant-time comparison before trusting a delivery.
Retry, disable, and delivery semantics
A failed delivery (anything other than a 2xx response, including a timeout) is retried up to 3 times with 1s/5s/25s backoff. A subscription that fails 10 consecutive deliveries is automatically disabled (visible on the dashboard); re-enable by deleting and re-creating it.
Webhooks are best-effort and at-least-once in normal operation, the same idempotency posture as most webhook providers — key on txid (plus status for tx: events) to de-duplicate on your end. Delivery is owned by exactly one backend node at a time; during the brief window around a failover (the prior owner stops, the new one takes over), events observed in that window are not delivered — webhooks are therefore at-most-once across a failover. Treat webhooks as a low-latency convenience and reconcile against the REST API for anything that must not be missed.
WhatsonChain-Compatible API
A WhatsonChain-compatible API is available at /api/v1/bsv/main. This allows existing applications built for the WoC API to work with BananaBlocks as a drop-in replacement. See the WhatsonChain API docs for endpoint details. BSV-21 token endpoints (/token/bsv21/...) are included — see the BSV-21 (Beta) contract for the covered subset.
This mirror is also where mempool-aware (unconfirmed) reads live, which the native /api/v1 endpoints above intentionally omit (they are confirmed-only): /address/<addr>/unconfirmed/unspent, /address/<addr>/unconfirmed/balance, and /address/<addr>/unconfirmed/history (plus their POST /addresses/... bulk forms).
The network segment follows the deployment, exactly as it does on WhatsonChain itself: mainnet serves /api/v1/bsv/main and testnet serves /api/v1/bsv/test, so an unmodified WoC client pointed at either host resolves without a path rewrite. This host serves /api/v1/bsv/main.
Known divergence — bulk confirmed balance
POST /addresses/confirmed/balance returns one extra key per entry: associatedScripts, the [{script,type}] list WhatsonChain ships only on the single-address /address/<addr>/confirmed/balance. Everything WhatsonChain does return is present and unchanged, so a client decoding its entry shape is unaffected; one decoding strictly (rejecting unknown keys) needs to allow it. The singular route, and both unconfirmed forms, match exactly.
Known divergence — per-block tag counts
GET /block/tagcount/height/<height>/stats matches WhatsonChain's route, response shape ({count, results:[{name,count}]}), count-descending order and status contract, but the tag names and counts are ours, not WhatsonChain's. Both sides count per (transaction, tag) and agree exactly on many tags, but WhatsonChain collapses a protocol and the app using it into a single hierarchical tag (run#haste) where we report both separately, and its unclassified OP_RETURN bucket is much broader than ours. Totals therefore differ. A client that reads count and iterates results works unmodified; one that switches on specific WhatsonChain tag names needs a mapping.
Two further notes: there is no by-hash variant of this endpoint (WhatsonChain has none either), and an unmined height returns 200 with zeros rather than a 404. Because zeros also mean “this block has no tagged transactions” — and, within a few blocks of the tip, “not indexed here yet” — treat an empty histogram near the tip as unknown rather than as none.