<!-- source: https://secondlayer.tools/docs/api/proofs -->

# Proofs API

Signed blocks, Bitcoin anchoring and state proofs, so you can check an answer instead of trusting it. How to run the checks is on [Verify](/docs/verification); envelope and cursors are in [REST conventions](/docs/rest-api).

## Signed blocks

A block with the signer set that approved it.

### Signed block by id

`GET /v1/proofs/block/{index_block_hash}`

The consensus-serialized Nakamoto block from the Stacks node (`/v3/blocks/{id}`): header with signer signatures, then transactions. Recompute the block id from the header and check the signatures against the cycle's signer set. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `index_block_hash` | string, required | The block's `index_block_hash`: 64 hex characters, `0x` optional. |

#### Responses

- `200` The block bytes. Immutable.
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` The node has no block with that `index_block_hash`
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/block/990152a894e12288960c8b68e5790ad7994f9d261e9ad8adc13c72e76f914339 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

### Signed block by height

`GET /v1/proofs/block/height/{height}`

The node's block at a Stacks height (`/v3/blocks/height/{height}`), same bytes as the by-id route. Near the tip the block at a height can change, so this is short-cached; prefer the by-id route once you know the id. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `height` | integer, required | Stacks block height. |

#### Responses

- `200` The block bytes.
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` The node has no block at that height
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/block/height/9048502 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

### Epoch 2.x block header by id

`GET /v1/proofs/epoch2-header/{index_block_hash}`

A pre-Nakamoto block's header from the Stacks node (`/v2/headers/1?tip={id}`), as the node's one-element array: `header` (consensus-serialized StacksBlockHeader, hex), `consensus_hash` and `parent_block_id`. The id is sha512/256(sha512/256(header) || consensus_hash): recompute it, then check state against the header's `state_index_root`. Nakamoto blocks are a 404 here; use the block route. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `index_block_hash` | string, required | The block's `index_block_hash`: 64 hex characters, `0x` optional. |

#### Responses

- `200` The block's header and consensus hash
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` The node has no epoch 2.x block with that `index_block_hash`
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/epoch2-header/990152a894e12288960c8b68e5790ad7994f9d261e9ad8adc13c72e76f914339 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

```json
[
  {
    "consensus_hash": "0898651bac6711e8c7679d76bdf5ffb11e9d6f7c",
    "header": "070000004b4c5f4fb400000000000249f0…",
    "parent_block_id": "d281730546550e66a5b734c49cba0867c3e379c6d39f7e819be44c64a9c03b9f"
  }
]
```

## Bitcoin anchoring

The Bitcoin blocks Stacks blocks are anchored to, and transactions in them.

### Burn block behind a consensus hash

`GET /v1/proofs/burn/{consensus_hash}`

The sortition preimage that hashes to a block header's `consensus_hash` (RIPEMD160(SHA256(preimage))). Bytes 4..36 of the preimage are the Bitcoin block hash, which pins the header's burn height against Bitcoin proof of work. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `consensus_hash` | string, required | The header's `consensus_hash`: 40 hex characters, `0x` optional. |

#### Returns

One `ProofBurnBlock` object.

#### Responses

- `200` The preimage and the burn block it names
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` No sortition with that consensus hash
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)
- `503` `PROOFS_UNAVAILABLE`: this instance has no proof sidecar (`PROOF_SIDECAR_URL` unset). `PROOF_SOURCE_BUSY`: the sidecar is busy; retry after `Retry-After` seconds

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/burn/e55512fc4a1fd7b3c6b0bcd1b0b2d28a7e3f9c11 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

```json
{
  "consensus_hash": "e55512fc4a1fd7b3c6b0bcd1b0b2d28a7e3f9c11",
  "burn_height": 970269,
  "bitcoin_block_hash": "00000000000000000001b6b3c5a9e2b5d8f8f1c0a7e4d3b2a1908f7e6d5c4b3a",
  "preimage": "0100000000000000000000000001b6b3c5a9e2b5d8f8f1c0a7e4d3b2a1908f7e6d5c4b3a…"
}
```

### Bitcoin block headers

`GET /v1/proofs/bitcoin-headers`

Raw 80-byte Bitcoin headers from `from`, at most 2016 (one difficulty period) per call. Check proof of work and linkage yourself from a checkpoint you trust. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `from` | integer, required | First Bitcoin height to return. |
| `count` | integer · 1 to 2016, required | Number of headers, 1 to 2016. |

#### Returns

One `BitcoinHeaders` object.

#### Responses

- `200` Consecutive headers starting at `from`
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` The sidecar has no header at `from`
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)
- `503` `PROOFS_UNAVAILABLE`: this instance has no proof sidecar (`PROOF_SIDECAR_URL` unset). `PROOF_SOURCE_BUSY`: the sidecar is busy; retry after `Retry-After` seconds

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/bitcoin-headers \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

```json
{
  "from": 0,
  "headers": [
    "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
  ]
}
```

### Bitcoin transaction proof

`GET /v1/proofs/bitcoin-tx/{txid}`

A confirmed Bitcoin transaction with its merkle branch (internal byte order) and block header, from the instance's own Bitcoin node. Recompute the txid from `raw_tx`, fold the branch to the header's merkle root, and check the header against a Bitcoin chain you trust, such as a checkpoint plus proof of work. Short-cached: a shallow block can reorg. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `txid` | string, required | The Bitcoin txid, display order: 64 hex characters, `0x` optional. |

#### Returns

One `BitcoinTxProof` object.

#### Responses

- `200` The raw transaction, merkle branch and block header
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` No confirmed transaction with that txid
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)
- `503` `PROOFS_UNAVAILABLE`: this instance has no Bitcoin node (`BITCOIN_RPC_URL` unset)

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/bitcoin-tx/f80a188d012dbb733748db003b987cc98fc4a636cbd071734decb298c1bad80b \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

```json
{
  "txid": "f80a188d012dbb733748db003b987cc98fc4a636cbd071734decb298c1bad80b",
  "block_hash": "0000000000000000000179d46b321695682e26183ee0b1257bb3ea17483be0f9",
  "block_height": 968461,
  "header": "0000003…",
  "raw_tx": "02000000000101…",
  "tx_index": 2156,
  "tx_count": 4371,
  "siblings": [
    "4c1a…"
  ]
}
```

## State proofs

Witnesses, writes and MARF inclusion proofs for state.

### State witness for a block

`GET /v1/proofs/witness/{index_block_hash}`

The block's MARF state witness (wire v3) from the proof sidecar: the trie nodes the block wrote, ancestor roots, and the names of every write. Recompute the root and compare it with the header's `state_index_root`; every changed leaf is a named write, a carried value, or an internal key, so a hidden write fails. Extracted on demand, so it has its own lower rate limit. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `index_block_hash` | string, required | The block's `index_block_hash`: 64 hex characters, `0x` optional. |

#### Responses

- `200` The witness bytes. Immutable.
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` The sidecar has no block with that `index_block_hash`
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)
- `503` `PROOFS_UNAVAILABLE`: this instance has no proof sidecar (`PROOF_SIDECAR_URL` unset). `PROOF_SOURCE_BUSY`: the sidecar is busy; retry after `Retry-After` seconds

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/witness/990152a894e12288960c8b68e5790ad7994f9d261e9ad8adc13c72e76f914339 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

### State writes of a block

`GET /v1/proofs/writes/{height}`

Every MARF write the canonical block at `height` committed, in the node's write order, from this instance's indexed `state_writes`: the names a verifier needs to label each written leaf of the block's witness. A write the witness does not hold, or a leaf no write names, fails that check, so the rows are checked, not trusted. 404 when the instance has no writes for the block (its node did not subscribe to `state_writes` at that height); verifiers then report the diff unnamed. Near the tip the block at a height can change, so this is short-cached. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `height` | integer, required | Stacks block height. |

#### Returns

One `BlockStateWrites` object.

#### Responses

- `200` The block's writes
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` No `state_writes` for a canonical block at that height
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)

#### Example

```bash
curl https://api.secondlayer.tools/v1/proofs/writes/9137005 \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY"
```

```json
{
  "block_height": 9137005,
  "state_writes": [
    {
      "ordinal": 0,
      "tx_index": 0,
      "key": "vm::SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01::1::paused",
      "value_hex": "3033"
    }
  ]
}
```

### MARF inclusion proof

`GET /v1/proofs/marf/{path}`

A value and its MARF inclusion proof as of block `tip`, from the node (`/v2/clarity/marf/{path}?proof=1`). Keys the node cannot serve because they hold no stored value string, such as the MARF's own `__MARF_BLOCK_HEIGHT_TO_HASH::<height>`, are proven by the proof sidecar from the MARF itself: same proof bytes, and `data` is the raw 40-byte leaf value. Fold the proof to a root and compare it with `tip`'s `state_index_root`. Inclusion only: a missing key is a 404, not a proof of absence. Free and keyless: never metered, no account needed. Hosted limits: 10 requests per second per IP (1 for witnesses), or 20 (2) per account with a key. Bytes are passed through unchanged; verify them, do not trust them.

#### Path parameters

| Name | Type | Description |
| --- | --- | --- |
| `path` | string, required | The MARF key hash: 64 hex characters, `0x` optional. |

#### Query parameters

| Name | Type | Description |
| --- | --- | --- |
| `tip` | string, required | `index_block_hash` of the block to prove against, 64 hex characters, `0x` optional. Pinned, so the answer is immutable. |

#### Returns

One `MarfProof` object.

#### Responses

- `200` The value and its proof
- `400` A path or query parameter is malformed (`VALIDATION_ERROR`): hashes must be exact-length hex, heights non-negative integers
- `401` This bind requires the instance token, and it was missing or wrong
- `404` No value at that path as of `tip`
- `429` Rate limited; retry after `Retry-After` seconds
- `502` The node or proof sidecar could not be reached, or answered with an unexpected status (`PROOF_SOURCE_ERROR`)

#### Example

```bash
curl -G https://api.secondlayer.tools/v1/proofs/marf/5c8d9e1f6c1e0f4a2a6a7f0b0f5f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b \
  -H "Authorization: Bearer $SECONDLAYER_API_KEY" \
  -d tip=990152a894e12288960c8b68e5790ad7994f9d261e9ad8adc13c72e76f914339
```

```json
{
  "data": "0x0100000000000000000000000000000064",
  "proof": "0x00…"
}
```

## Objects

### The Burn block proof object

A sortition's consensus-hash preimage and the Bitcoin block it commits to.

| Field | Type | Description |
| --- | --- | --- |
| `consensus_hash` | string | The consensus hash asked about, hex. |
| `burn_height` | integer | Bitcoin height of the sortition. |
| `bitcoin_block_hash` | string | Bitcoin block hash, display order. Equals preimage bytes 4..36. |
| `preimage` | string | Hex bytes whose RIPEMD160(SHA256(·)) is `consensus_hash`. |

```json
{
  "consensus_hash": "e55512fc4a1fd7b3c6b0bcd1b0b2d28a7e3f9c11",
  "burn_height": 970269,
  "bitcoin_block_hash": "00000000000000000001b6b3c5a9e2b5d8f8f1c0a7e4d3b2a1908f7e6d5c4b3a",
  "preimage": "0100000000000000000000000001b6b3c5a9e2b5d8f8f1c0a7e4d3b2a1908f7e6d5c4b3a…"
}
```

### The Bitcoin headers object

Consecutive raw Bitcoin block headers.

| Field | Type | Description |
| --- | --- | --- |
| `from` | integer | Bitcoin height of the first header. |
| `headers` | array of string | 80-byte headers, hex, in height order. |

```json
{
  "from": 0,
  "headers": [
    "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c"
  ]
}
```

### The Bitcoin transaction proof object

A Bitcoin transaction, the merkle branch to its block's root, and that block's header.

| Field | Type | Description |
| --- | --- | --- |
| `txid` | string | The txid, display order, hex. |
| `block_hash` | string | Hash of `header`, display order, hex. |
| `block_height` | integer | Bitcoin height of the confirming block. |
| `header` | string | The block's 80-byte header, hex. |
| `raw_tx` | string | The serialized transaction, witness included, hex. |
| `tx_index` | integer | Position of the transaction in the block. |
| `tx_count` | integer | Number of transactions in the block. |
| `siblings` | array of string | Merkle branch from the leaf up, internal byte order, hex: `ceil(log2(tx_count))` hashes. |

```json
{
  "txid": "f80a188d012dbb733748db003b987cc98fc4a636cbd071734decb298c1bad80b",
  "block_hash": "0000000000000000000179d46b321695682e26183ee0b1257bb3ea17483be0f9",
  "block_height": 968461,
  "header": "0000003…",
  "raw_tx": "02000000000101…",
  "tx_index": 2156,
  "tx_count": 4371,
  "siblings": [
    "4c1a…"
  ]
}
```

### The Block state writes object

One block's MARF writes, whole, in write order.

| Field | Type | Description |
| --- | --- | --- |
| `block_height` | integer | Stacks block height. |
| `state_writes` | array of object | Every write of the block, ordinal 0 first. |

```json
{
  "block_height": 9137005,
  "state_writes": [
    {
      "ordinal": 0,
      "tx_index": 0,
      "key": "vm::SP102V8P0F7JX67ARQ77WEA3D3CFB5XW39REDT0AM.amm-vault-v2-01::1::paused",
      "value_hex": "3033"
    }
  ]
}
```

### The MARF proof object

A MARF value and its inclusion proof, as the node returns them.

| Field | Type | Description |
| --- | --- | --- |
| `data` | string | The stored value, hex (`0x`-prefixed). For keys with no stored value (`__MARF_*`), the raw 40-byte leaf value instead. |
| `proof` | string | Serialized MARF proof, hex (`0x`-prefixed). Proof bytes are malleable: hash the parsed content, never use them as an id. |

```json
{
  "data": "0x0100000000000000000000000000000064",
  "proof": "0x00…"
}
```

## Notes for agents

- Base: 'GET $SECONDLAYER_API_URL/v1/proofs/<endpoint>' with 'Authorization: Bearer $SECONDLAYER_API_KEY'.
- Check proofs locally with @secondlayer/verify rather than trusting the response body.
- One endpoint as markdown: 'https://secondlayer.tools/docs/api-reference/<anchor>.md', e.g. get-signed-block.

## Setup

Ensure setup once, skipping any step already done:
- Skill: `bunx skills add ryanwaits/secondlayer --skill secondlayer -y`
- CLI: `bun add -g @secondlayer/cli`
- Instance: `secondlayer init` writes `INSTANCE_TOKEN`. Loopback `/v1` reads need no key.
- Hosted/archive: export `SECONDLAYER_API_KEY` (`sk-sl_*`) or buy credits with `secondlayer credits buy` (`balance`, `refill`), then `secondlayer whoami`
