# AGENTS.md — using this service from an agent

This is a Bitcoin data service: indexed chain data over HTTP, plus entity attribution and
transaction analysis. You do not need a browser, an email address, or a human to start using it.

Everything below is the whole contract. If something here disagrees with a rendered page, this file
is right.

## 1. Get a key (one request, no auth)

```
POST /v1/accounts
Content-Type: application/json
Idempotency-Key: <any unique string you can reproduce>

{"contact": "optional, for receipts only"}
```

```json
{
  "account_id": 1,
  "key": "cbk_…",              // shown ONCE. It is not recoverable; make another if you lose it.
  "key_id": 2,
  "plan": "free",
  "limits": { "per_minute": 120, "per_day": 1000, "resets": "00:00 UTC" },
  "usage_url": "/v1/usage",
  "docs_url": "/v1/openapi.json"
}
```

Send `Idempotency-Key` — if your request is retried after a timeout, the second attempt returns
`409` with the `account_id` it already created instead of making a second account. This matters:
agents retry, and a duplicate account means your key is on the account you are not looking at.

## 2. Use it

```
GET /v1/usage
Authorization: Bearer cbk_…
```

Every response from a keyed endpoint carries its quota, so you can throttle before you are
throttled. Hitting the daily cap does not lock you out of your own account: `/v1/usage`,
`/v1/accounts/me` and key management keep working, because that is how you find out what happened.

```
X-RateLimit-Limit-Day: 1000
X-RateLimit-Remaining-Day: 993
X-RateLimit-Limit-Minute: 120
```

## 3. Endpoints

| method + path | auth | what it does |
|---|---|---|
| `GET /health` | none | liveness and chain tip |
| `GET /v1` | none | service descriptor (this file, machine-readable) |
| `POST /v1/accounts` | none | create an account, get the first key |
| `GET /v1/pricing` | none | plans and their limits, as JSON |
| `GET /v1/openapi.json` | none | OpenAPI 3.1 |
| `GET /v1/accounts/me` | key | your account, plan, keys (ids and labels only) |
| `POST /v1/accounts/keys` | key | add a key: `{"label": "…", "scopes": "api"}` |
| `POST /v1/keys/{id}/disable` | key | disable one of your keys |
| `GET /v1/usage` | key | today's usage, your limits, the last days |

### Data endpoints (all require a key)

| method + path | what it does |
|---|---|
| `GET /v1/address/{address}/txs` | transaction history, newest first, paged |
| `GET /v1/address/{address}/utxos` | unspent outputs, paged |
| `GET /v1/address/{address}/analyze` | summary: balance, totals, counts, first/last seen |
| `GET /v1/address/{address}/counterparties` | who this address transacted with, over its whole history |
| `GET /v1/utxo-index/stats` | coverage of the coin index |
| `GET /v1/chain/info` | chain tip and sync state |
| `GET /v1/latest` | the newest block |
| `GET /v1/block/{height}/analyze` | one block, decoded |
| `GET /v1/tx/{txid}/analyze` | one transaction, decoded |
| `GET /v1/tx/{txid}/facts` | the transaction's key facts |
| `GET /v1/tx/{txid}/change` | which outputs look like change, and why |
| `GET /v1/tx/{txid}/content` | embedded data: inscriptions, OP_RETURN |
| `GET /v1/tx/{txid}/graph_data` | the graph form the UI draws |
| `GET /v1/tx/{txid}/is_inscription` | cheap inscription test |
| `GET /v1/tx/{txid}/related` | transactions related to this one |

### The node's current state

| method + path | what it does |
|---|---|
| `GET /v1/fees` | fee estimates in sat/vB for 1, 2, 3, 6, 12, 24 and 144 blocks |
| `GET /v1/fees/recommended` | the same estimates as `fastest`, `half_hour`, `hour`, `economy`, plus `minimum` |
| `GET /v1/mempool` | pending transaction count, serialized bytes, total fee, minimum relay fee |
| `GET /v1/mempool/index` | the index itself: tracked transactions, vsize, total fee, projected blocks, snapshot age and tip |
| `GET /v1/mempool/txids` | every pending txid |
| `GET /v1/mempool/recent` | the 10 newest pending transactions: `txid`, `fee`, `vsize`, `value` |
| `GET /v1/fees/mempool-blocks` | the pool packed into up to 8 projected blocks, each with `nTx`, `blockVSize`, `medianFee` and a 7-point `feeRange` |
| `GET /v1/replacements` | the newest replacement trees observed (up to 25, kept for an hour) |
| `GET /v1/fullrbf/replacements` | the same, only trees containing a full-RBF replacement |
| `GET /v1/blocks/tip/height` | the tip height |
| `GET /v1/blocks/tip/hash` | the tip height and hash |

`0` in a fee estimate means the node had no estimate for that target — not that a zero fee would
confirm. `/v1/mempool` is the node's own summary: count and total fee, no vsize or histogram. The
`/v1/mempool/*` routes answer from the index instead, which does carry both, and they answer 503 (not
an empty array) until the first snapshot exists — an empty pool and a warming index are different
facts. Replacement trees are what this node observed while the service ran: a replacement from
before a restart is absent.

### Electrum (for wallets, not HTTP)

Wallets speak the Electrum protocol, not HTTP, so this is a separate port and a separate transport:

```
host       electrum.blocknil.com
port       50002
transport  TLS required — no plaintext Electrum port is open
auth       none
```

The Electrum protocol has no field for a key, so there is nowhere to put one: this endpoint is not
keyed. It is capped per source address (5 concurrent connections), and it answers from the **same
index** that `/v1` does, so history, balances and unspent outputs agree across the two. The history
cut-off is the same 25,000 entries, so a very large address is refused here exactly as it is over
HTTP.

```
# from a shell, no wallet needed
openssl s_client -connect electrum.blocknil.com:50002 -servername electrum.blocknil.com

# Electrum, Sparrow, BlueWallet, Nunchuk — server string with the trailing :s meaning SSL
electrum.blocknil.com:50002:s
```

Fee estimates here are the node's own, and the same caveat applies: a `0` from
`blockchain.estimatefee` means the node had no estimate for that target, not that a zero fee would
confirm.

### Not available yet

Entity attribution — named entities, their addresses, their activity — is deliberately not sold yet
while it gets finished. The entity paths do not exist under `/v1`: calling one with a valid key
returns `404`. The tables above list everything that exists today, and this note will shrink as that
changes.

**Paging.** `?page=` and `?limit=` (default 25, maximum 100). Every paged answer states whether more
exists — never assume a short page means the end of the history. Asking for a page that does not
exist returns an empty page rather than an error: no address is ever refused for having too many
transactions.

**Unversioned paths.** The same handlers also answer without the `/v1` prefix and without a key,
because the existing UI calls them there. Treat those as internal — they require a key once the move
finishes, and anything you build should use `/v1`.

## 4. Errors — every one names the fix

| status | meaning | what to do |
|---|---|---|
| `401` | no key, or a key that is not ours | `POST /v1/accounts`, then send `Authorization: Bearer <key>` |
| `403` | key disabled, account disabled, or the key lacks the scope | ask the account owner; another key may have the scope |
| `429` | burst or daily quota exhausted | read `Retry-After` (seconds) and the `X-RateLimit-*` headers; the daily one resets at 00:00 UTC |
| `503` | the global request ceiling for this second is full | back off a moment and retry; this is us protecting the shared node, not a problem with your key |

Rate limits are per key (burst) and per account (daily). The free tier is deliberately small; it is
meant for building against, not for bulk extraction.

## 4a. The `/api` surface, no key

A second set of paths serves the same data without a key, in the layout most Bitcoin REST clients
already expect, so a client built for that layout works here unchanged:

```
GET /api/blocks/tip/height              GET /api/block/{hash}
GET /api/blocks/tip/hash                GET /api/block/{hash}/header
GET /api/address/{address}              GET /api/block/{hash}/raw
GET /api/address/{address}/txs          GET /api/block/{hash}/status
GET /api/address/{address}/txs/chain[/{last_seen_txid}]
GET /api/address/{address}/txs/mempool  GET /api/block/{hash}/txid/{index}
GET /api/address/{address}/utxo         GET /api/block/{hash}/txids
GET /api/v1/validate-address/{address}  GET /api/block/{hash}/txs[/{start_index}]
GET /api/blocks[/{start_height}]        GET /api/block-height/{height}
GET /api/v1/blocks-bulk/{min}[/{max}]   GET /api/v1/mining/blocks/timestamp/{timestamp}
GET /api/mempool                        GET /api/v1/fees/mempool-blocks
GET /api/mempool/txids                  GET /api/v1/replacements
GET /api/mempool/recent                 GET /api/v1/fullrbf/replacements
GET /api/v1/cpfp/{txid}                 GET /api/v1/block/{hash}/audit-summary
GET /api/v1/mining/blocks/audit/score/{hash}
GET /api/v1/mining/blocks/audit/scores[/{height}]
```

**Same paths, same JSON shapes.** `/api/blocks/tip/height` returns `972174` (a bare value, not an
object), arrays come back as arrays, and transaction objects carry `vin` / `vout` / `status` — a
parser written for this layout reads these responses unchanged.

Six things are still ours, and each is visible rather than silent:

* `sigops` is present and computed the way consensus counts it (legacy x4, witness x1, P2SH redeem
  split across both sides, output scripts included).
* `/api/address/{address}/txs/mempool` is `[]` — the mempool is indexed by transaction, not by
  address.
* the mempool paths are one node's pool, re-read every 5 seconds. `X-Mempool-Updated` carries the
  snapshot's unix time; for the few seconds after a restart, before the first snapshot, they answer
  `503`. Replacements are the ones this node saw while the service ran (the newest 25 trees, kept
  an hour).
* `funded_txo_count` / `spent_txo_count` are `null` for addresses over 2,000 transactions, where
  counting costs more than the answer is worth.
* block audits exist for blocks this service saw arrive (summaries kept 30 days, scores
  indefinitely); `acceleratedTxs` is always empty, and template `flags` infer input types from
  witness shapes.
* an audit measures **our** template against the mined block, from **this node's** pool
  (`templateAlgorithm` 0, our packer). It is not a copy of anyone else's audit and its numbers
  will not equal theirs: expect the template size to differ by a few percent, `unseenTxs` to be
  non-empty (transactions the miner included that this node never saw), and
  `fullrbfReplacements` to be 0 until the service has watched a replacement itself. Those are
  measurements, not defects: what the miner did is fixed, what each service could see is not.
* there is no `POST /api/tx`, and no mining statistics, Lightning or price history. UTXO order is
  ours (chain order); the set and every field match.

**No key on these paths.** That is what makes them drop-in; the global per-second ceiling still
applies. The `/v1` equivalents give you the same data with richer payloads, per-key quotas and a
`Retry-After` you can plan around — use them when you are building, and these when you are migrating.

Three things genuinely do not exist here, and no amount of remapping will produce them:
`/txs/mempool` is always `[]` (the mempool is indexed by transaction, not by address), there is no
broadcast (`POST /api/tx`), and mining statistics, Lightning and price history are not indexed. `:last_seen_txid` paging works by walking up to 20,000 entries; beyond that it says so
instead of guessing — the seek-based cursor is `/v1/address/{address}/txs?after=p:…`.

## 5. What this service will not do

It will not ask you to solve a CAPTCHA, receive an email, or click a checkout — if any of those ever
appear in this flow, that is a bug, and the flow has stopped being agent-usable. There is no
custody, no signing, and no financial advice: this is data and connectivity.
