$ blocknet API v0.12.1

Authenticated JSON API for the Blocknet privacy blockchain daemon. All private endpoints require Bearer token authentication via a cookie file (`<dataDir>/api.cookie`) written on daemon startup. Public routes (GET-only blockchain queries) are also served unauthenticated when using `--explorer` mode.

# authentication

Token read from `<dataDir>/api.cookie`. Generated fresh on every daemon startup using `crypto/rand` (32 bytes, hex-encoded = 64 chars). File has 0600 permissions. Deleted on graceful shutdown.

curl -H "Authorization: Bearer $(cat data/api.cookie)" http://127.0.0.1:8332/api/status
Node Blockchain Network Wallet Mining Events Admin

# node

Daemon and node status

GET/api/health

Lightweight liveness probe. Returns 200 with the current server timestamp. Does not require authentication.

Response 200

{
  "status": "ok",
  "timestamp": "2026-03-11T18:30:00Z"
}
GET/api/status

Returns node and chain status including peer count, chain height, sync state, mempool summary, and wallet diagnostics (if unlocked).

Response 200

{
  "peer_id": "12D3KooWBLUPjZgAoizTkbNJvHxgHkb2tF1saUhmKBzFfo8zZLfj",
  "peers": 8,
  "chain_height": 14207,
  "best_hash": "0000c3a5b7e2d1f4",
  "total_work": 14208,
  "mempool_size": 3,
  "mempool_bytes": 4821,
  "syncing": false,
  "identity_age": "2h34m12s",
  "wallet": {
    "data_version": 1,
    "enc_format": "v1",
    "kdf_version": 1,
    "kdf_memory_mib": 256,
    "kdf_iterations": 3,
    "kdf_threads": 4,
    "created_at": 1710000000,
    "view_only": false,
    "has_mnemonic": true,
    "address_format": "checksummed",
    "file_size_bytes": 2048,
    "synced_height": 14200
  }
}

Errors

401 unauthorized
POST/api/verify

Verifies an ed25519 signature against a Blocknet stealth address. Does not require a wallet — uses only stateless cryptographic verification. Useful for 'login with Blocknet' flows where any node can validate a signature produced by `POST /api/wallet/sign`. Rate-limited per IP.

Request

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200",
  "signature": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}
address required string
Blocknet stealth address (base58-encoded)
message required string
The message that was signed (max 1024 bytes)
signature required string
Hex-encoded 64-byte ed25519 signature (128 hex chars)

Response 200 — Valid signature

{
  "valid": true
}

Response 200 — Invalid signature

{
  "valid": false
}

Errors

400 { "error": "invalid address" }
400 { "error": "invalid signature: must be 64 bytes hex-encoded" }
400 { "error": "message is required" }
429 { "error": "verify rate limit exceeded" }

# blockchain

Block, transaction, and mempool queries

GET/api/block/{id}

Returns a block by numeric height or 64-character hex hash. Includes transaction summaries and confirmation count.

Parameters

id required path · string
Block height (integer) or block hash (64 hex characters)

Response 200

{
  "height": 14207,
  "hash": "0000c3a5b7e2d1f489a1bc37e5d204f8c612aa9b33e7f04d12b8a9e6c7f50321",
  "prev_hash": "0000a1f38e2bc9d7a436ef21c87b3d4e59a0f1b267e3c9d58a2b41f6e7083c19",
  "merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "timestamp": 1738800120,
  "difficulty": 100000,
  "nonce": 847291,
  "tx_count": 2,
  "transactions": [
    {
      "hash": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "is_coinbase": true,
      "inputs": 0,
      "outputs": 1,
      "fee": 0
    },
    {
      "hash": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "is_coinbase": false,
      "inputs": 1,
      "outputs": 2,
      "fee": 15000
    }
  ],
  "confirmations": 1,
  "reward": 72325093035
}

Errors

400 { "error": "id must be a height or 64-char hex hash" }
401 unauthorized
404 { "error": "block not found" }
GET/api/tx/{hash}

Searches mempool first (fast), then scans the blockchain from tip backwards. Returns the transaction with confirmation count and mempool status.

Parameters

hash required path · string (^[a-f0-9]{64}$)
Transaction hash (64 hex characters)

Response 200 — Confirmed transaction (in blockchain)

{
  "tx": {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "range_proof": "UTfDE/BxZuuznHRyDGLMqI4jjrPMqQ47hVuHEzfesKDfO8VhghbfAGS63COpoD+ZntGnzpdBYtfCWZrPAJuSa9yk7uLibfJWK5GrL3iec2VLDBd98yXp1GPE/cx8SwI22XBa7Rl/PulE7aLi2uRR8+aEfo34eozhJ5J4i6ujKUZNdsRObSDU0Knu1B9p18cKwvQDtJjH1nD5cIvf+A7HrM9U70ENyQ0q20XsXRmFwqds6Keswo7XgSnwCRqzciMUD35mCk56QPI6b+6DvFU6U583DZ/Ay2UmfDSaPRWx270jrgbX+jbduetO3lqK9+7fiaV9LI7mfO3CrA79pl35bLWEro+NBWEre9D6e/P75QgvlnHPfJy88rDZqbToipyAdj1ioT1eYm73jZAzY5d0uFuaB0CMFxuVQPs0BpHw9eGuXhqB9DohzfslG01Mmyt/PNVzwubimNucHjJqbIcpUHpYJlAB0ebwlRB2k5DoJHeHZdk6c0yISCQeVJ2T4D/vm86L/OApFN2lgA0udQqJFFnw4o5c3/su8LLRqqQ1UqjS/ZPNEugtoYGlO84A7NMbYLn/4hpoiEOT4Pg+DnpRnwfQL3M67DxO/5WL1PfxfOlKxGFFI43UrogBkJj6TOT3sKrB6aRgesR30hai8sPFTf0SQKkz4TPpB0nRTybwh63LKajCovkSI3iTdC7eMjPjVZkOF6Yclre/3Ep90lxXWSjDe/5JduyC64IE7pNQJeKwmdmA6ZplxPc2ecO3l5cLyowEGf6SdbRwYYBGMRSe4RG6Qy6Xp9RZZkO7i1SD9petOu8mSHPLuy7KB4c/6LyGw743d/EMp3Eg7ZrRO0cXE5v8OzF4RcbovdZP1DL60I8QvW/j43i5Mry3H8uNYT7o",
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 15000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  },
  "block_height": 14205,
  "confirmations": 3,
  "in_mempool": false
}

Response 200 — Unconfirmed transaction (in mempool)

{
  "tx": {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "range_proof": "UTfDE/BxZuuznHRyDGLMqI4jjrPMqQ47hVuHEzfesKDfO8VhghbfAGS63COpoD+ZntGnzpdBYtfCWZrPAJuSa9yk7uLibfJWK5GrL3iec2VLDBd98yXp1GPE/cx8SwI22XBa7Rl/PulE7aLi2uRR8+aEfo34eozhJ5J4i6ujKUZNdsRObSDU0Knu1B9p18cKwvQDtJjH1nD5cIvf+A7HrM9U70ENyQ0q20XsXRmFwqds6Keswo7XgSnwCRqzciMUD35mCk56QPI6b+6DvFU6U583DZ/Ay2UmfDSaPRWx270jrgbX+jbduetO3lqK9+7fiaV9LI7mfO3CrA79pl35bLWEro+NBWEre9D6e/P75QgvlnHPfJy88rDZqbToipyAdj1ioT1eYm73jZAzY5d0uFuaB0CMFxuVQPs0BpHw9eGuXhqB9DohzfslG01Mmyt/PNVzwubimNucHjJqbIcpUHpYJlAB0ebwlRB2k5DoJHeHZdk6c0yISCQeVJ2T4D/vm86L/OApFN2lgA0udQqJFFnw4o5c3/su8LLRqqQ1UqjS/ZPNEugtoYGlO84A7NMbYLn/4hpoiEOT4Pg+DnpRnwfQL3M67DxO/5WL1PfxfOlKxGFFI43UrogBkJj6TOT3sKrB6aRgesR30hai8sPFTf0SQKkz4TPpB0nRTybwh63LKajCovkSI3iTdC7eMjPjVZkOF6Yclre/3Ep90lxXWSjDe/5JduyC64IE7pNQJeKwmdmA6ZplxPc2ecO3l5cLyowEGf6SdbRwYYBGMRSe4RG6Qy6Xp9RZZkO7i1SD9petOu8mSHPLuy7KB4c/6LyGw743d/EMp3Eg7ZrRO0cXE5v8OzF4RcbovdZP1DL60I8QvW/j43i5Mry3H8uNYT7o",
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 12000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  },
  "confirmations": 0,
  "in_mempool": true
}

Errors

400 { "error": "hash must be 64 hex characters" }
401 unauthorized
404 { "error": "transaction not found" }
GET/api/mempool

Returns mempool transaction count, total size in bytes, and fee statistics (min, max, average) in atomic units.

Response 200

{
  "count": 5,
  "size_bytes": 8420,
  "min_fee": 10000,
  "max_fee": 25000,
  "avg_fee": 14600.0
}

Errors

401 unauthorized
GET/api/mempool/txs

Returns all currently unconfirmed mempool transactions as full transaction objects in a JSON array. Each item matches the same `Transaction` schema returned by `GET /api/tx/{hash}`. Returns an empty array when the mempool is empty.

Response 200 — Mempool has transactions

[
  {
    "version": 1,
    "inputs": [
      {
        "key_image": [206,194,102,91,117,127,68,44,128,196,45,250,102,215,110,191,198,108,77,236,91,173,42,44,240,161,206,21,137,3,217,104],
        "ring_members": [
          [226,131,206,64,181,243,28,67,77,209,63,49,60,73,151,89,90,209,74,106,163,255,119,131,60,185,95,37,147,5,24,172],
          [110,193,204,172,204,197,212,189,47,92,19,190,204,227,117,159,115,159,54,160,111,127,196,151,224,41,224,215,131,35,150,164]
        ],
        "ring_commitments": [
          [244,65,131,220,148,209,81,144,194,225,99,194,172,101,176,2,181,151,98,87,133,213,118,209,176,204,1,57,103,0,158,85],
          [227,105,135,213,239,147,102,10,53,157,38,16,29,213,153,189,151,99,51,202,204,88,135,235,168,65,66,228,199,214,91,184]
        ],
        "pseudo_output": [185,41,236,101,236,26,183,66,110,82,226,90,90,18,56,11,87,242,214,41,217,44,73,158,2,195,168,69,207,102,28,108],
        "ring_signature": "gGqM7cNe9fNWJnrrBzqo/CSrAowGo9OFVEUYyxIYr3WVUeAd1XVgO/hXi426JGVYtSk6QEZMLBDCVbV2Uq4br99gnVs4S/4GdsAxeOMpUlXjv4GgF8sKmVFDBZKXlD/8"
      }
    ],
    "outputs": [
      {
        "commitment": [57,12,140,125,114,71,52,44,216,16,15,47,111,119,13,101,214,112,229,142,3,81,216,174,142,79,110,172,52,47,194,49],
        "public_key": [183,176,135,22,235,63,193,40,150,185,98,35,23,116,148,40,119,51,194,142,232,186,83,189,181,107,136,36,87,125,83,236],
        "encrypted_amount": [81,216,32,197,195,239,128,5],
        "encrypted_memo": [58,136,174,57,150,222,80,232,1,134,91,54,152,101,78,191,82,0,165,250,9,57,185,157,122,29,123,40,43,248,35,64,65,243,84,135,216,108,102,159,204,191,224,231,61,126,115,32,173,10,117,112,3,36,30,117,34,16,169,36,121,142,248,109,67,242,124,242,208,97,48,49,220,181,216,210,239,27,50,31,206,173,55,127,98,97,229,71,216,93,142,236,127,38,226,50,25,7,47,121,85,208,248,246,109,205,30,84,194,1,199,135,232,146,216,249,79,97,151,111,29,31,160,29,25,244,80,29]
      }
    ],
    "fee": 12000,
    "tx_public_key": [194,138,112,166,28,117,16,161,205,137,33,108,161,108,255,202,234,73,135,71,126,134,219,204,185,112,70,252,46,24,56,78]
  }
]

Response 200 — Mempool is empty

[]

Errors

401 unauthorized
GET/api/certify

Walks the entire chain and checks every block's difficulty (LWMA), timestamp rules, and prev-hash linkage. This is an arithmetic-only check (no Argon2id PoW re-hashing), so it completes in seconds. Returns all violations found, or confirms the chain is clean.

Response 200 — Chain is clean

{
  "height": 14207,
  "clean": true
}

Response 200 — Violations found

{
  "height": 14207,
  "clean": false,
  "violations": [
    {
      "height": 8042,
      "message": "difficulty mismatch: expected 1234 got 5678"
    },
    {
      "height": 8043,
      "message": "timestamp not greater than median of last 11 blocks"
    }
  ]
}

Errors

401 unauthorized

# network

Peer connectivity and ban management

GET/api/peers

Returns the list of currently connected libp2p peers with their peer IDs and remote multiaddrs.

Response 200

{
  "count": 3,
  "peers": [
    {
      "peer_id": "12D3KooWBLUPjZgAoizTkbNJvHxgHkb2tF1saUhmKBzFfo8zZLfj",
      "addrs": [
        "/ip4/203.0.113.10/tcp/28080"
      ]
    },
    {
      "peer_id": "12D3KooWN7z3vEqr4C8pMk2axGfRbLVJzqPEasiY4L9DMqFjVnGq",
      "addrs": [
        "/ip4/198.51.100.22/tcp/28080"
      ]
    },
    {
      "peer_id": "12D3KooWQe1RkTvGvbFYm6G3bNzdpJRcmAQA4f9bUDkYcFWLdM4P",
      "addrs": [
        "/ip4/192.0.2.5/tcp/28080"
      ]
    }
  ]
}

Errors

401 unauthorized
GET/api/peers/banned

Returns the list of banned peers with ban reason, count, permanence, and expiry time.

Response 200

{
  "count": 2,
  "banned": [
    {
      "peer_id": "12D3KooWMalicious111111111111111111111111111111111111",
      "addrs": [
        "/ip4/203.0.113.50/tcp/28080"
      ],
      "reason": "sent invalid block",
      "ban_count": 3,
      "permanent": false,
      "expires_at": "2026-02-06T19:30:00Z"
    },
    {
      "peer_id": "12D3KooWMalicious222222222222222222222222222222222222",
      "addrs": [
        "/ip4/198.51.100.99/tcp/28080"
      ],
      "reason": "excessive invalid transactions",
      "ban_count": 5,
      "permanent": true
    }
  ]
}

Errors

401 unauthorized

# wallet

Balance, address, history, and sending. In `--daemon` mode the node starts without a wallet — call `POST /api/wallet/load` first. All other wallet endpoints require a loaded, unlocked wallet (except lock/unlock themselves).

POST/api/wallet/load

Loads an existing wallet from disk, decrypting with the given password. Primarily used in `--daemon` mode where the app starts without a wallet. If `filepath` is provided, only the basename is used and the file is resolved relative to the configured `--wallet` directory; if omitted, the default wallet path is used. After loading, the wallet is unlocked, all wallet endpoints become available, miner reward keys are pointed at this wallet, and any blocks that arrived before the wallet was loaded are scanned automatically. Can only be called once — returns 409 if a wallet is already loaded.

Request

{
  "password": "my-wallet-passphrase",
  "filepath": "mywallet.dat"
}
password required string
Wallet encryption password (min 3 characters). Used to decrypt the wallet file.
filepath string
Optional wallet filename (basename only, no path separators). Resolved relative to the configured `--wallet` directory. Defaults to the configured `--wallet` path.

Response 200

{
  "loaded": true,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "mywallet.dat"
}

Errors

400 { "error": "invalid JSON body" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filepath" }
401 { "error": "unauthorized" }
401 { "error": "wrong password" }
403 { "error": "wallet file not readable (permission denied)" }
404 { "error": "wallet file not found" }
409 { "error": "wallet already loaded" }
422 { "error": "wallet file is corrupted" }
500 { "error": "internal error" }
POST/api/wallet/create

Generates a new wallet with a fresh BIP39 seed, encrypts it with the given password, and saves it to disk. The wallet is loaded and unlocked in the running daemon. Since this is a brand-new wallet, no chain scanning is needed — the synced height is set to the current chain tip. Can only be called when no wallet is currently loaded (returns 409 otherwise). If `filename` is provided, only the basename is used and the file is placed in the same directory as the configured `--wallet` path; if omitted, the default wallet path is used. Will not overwrite an existing file.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Encryption password for the wallet file (min 3 characters).
filename string
Optional wallet filename (basename only, no path separators). Defaults to the configured `--wallet` path.

Response 200

{
  "created": true,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "wallet.dat"
}

Errors

400 { "error": "invalid JSON body" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filename" }
401 unauthorized
409 { "error": "wallet already loaded" }
409 { "error": "wallet file already exists: wallet.dat" }
500 { "error": "internal error" }
POST/api/wallet/unload

Locks and unloads the currently loaded wallet, releasing all in-memory key material. After this call the node returns to the same state as before load was called — all /api/wallet/* endpoints return 503 until a wallet is loaded again. Mining reward keys are cleared. No request body required.

Response 200

{
  "unloaded": true
}

Errors

401 unauthorized
503 { "error": "no wallet loaded" }
POST/api/wallet/import

Creates a new wallet from a BIP39 12-word recovery seed, encrypts it with the given password, and saves it to disk. The wallet is loaded and unlocked in the running daemon and the entire blockchain is scanned for outputs belonging to this seed. Can only be called when no wallet is currently loaded (returns 409 otherwise). If `filename` is provided, only the basename is used and the file is placed in the same directory as the configured `--wallet` path; if omitted, the default wallet path is used. Will not overwrite an existing file.

Request

{
  "mnemonic": "abandon ability able about above absent absorb abstract absurd abuse access accident",
  "password": "my-wallet-passphrase",
  "filename": "restored.dat"
}
mnemonic required string
BIP39 12-word recovery phrase
password required string
Encryption password for the wallet file (min 3 characters)
filename string
Optional wallet filename (basename only, no path separators). Defaults to the configured --wallet path.

Response 200

{
  "imported": true,
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "filename": "restored.dat"
}

Errors

400 { "error": "invalid JSON body" }
400 { "error": "mnemonic is required" }
400 { "error": "invalid mnemonic phrase" }
400 { "error": "password must be at least 3 characters" }
400 { "error": "invalid filename" }
401 unauthorized
409 { "error": "wallet already loaded" }
409 { "error": "wallet file already exists: restored.dat" }
500 { "error": "failed to create wallet: invalid mnemonic: checksum mismatch" }
GET/api/wallet/balance

Returns spendable, pending, and total confirmed balance in atomic units (1 BNT = 100,000,000 atomic units). Spendable excludes immature coinbase outputs. Also includes an optional UX-only `pending_unconfirmed` amount (typically change from your most recent send) plus an estimated unlock ETA in seconds, assuming ~5 minute blocks. Requires unlocked wallet.

Response 200

{
  "spendable": 1250000000,
  "pending": 72325093035,
  "pending_unconfirmed": 0,
  "pending_unconfirmed_eta": 0,
  "total": 73575093035,
  "outputs_total": 14,
  "outputs_unspent": 9,
  "chain_height": 14207
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/address

Returns the wallet's public stealth address (base58-encoded spend_pub || view_pub, 64 bytes). Senders derive unique one-time addresses from this. Requires unlocked wallet.

Response 200

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "view_only": false
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/history

Returns wallet outputs (spendable UTXOs) with amounts, block heights, coinbase flag, and spent status. Requires unlocked wallet.

Response 200

{
  "count": 3,
  "outputs": [
    {
      "txid": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "output_index": 0,
      "amount": 72325093035,
      "block_height": 14200,
      "is_coinbase": true,
      "spent": false
    },
    {
      "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "output_index": 0,
      "amount": 500000000,
      "block_height": 14100,
      "is_coinbase": false,
      "spent": false
    },
    {
      "txid": "f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
      "output_index": 1,
      "amount": 750000000,
      "block_height": 13990,
      "is_coinbase": false,
      "spent": true,
      "spent_height": 14050
    }
  ]
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
GET/api/wallet/outputs

Returns every output owned by the wallet with computed status (spent/unspent/pending), confirmations, type, cryptographic keys, and summary counts. Sorted ascending by block height, then txid, then output index. Requires unlocked wallet.

Response 200

{
  "chain_height": 14207,
  "synced_height": 14207,
  "total": 3,
  "spent": 1,
  "unspent": 1,
  "pending": 1,
  "outputs": [
    {
      "txid": "f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d",
      "output_index": 1,
      "amount": 750000000,
      "status": "spent",
      "type": "regular",
      "confirmations": 217,
      "block_height": 13990,
      "spent_height": 14050,
      "one_time_pub": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "commitment": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef"
    },
    {
      "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "output_index": 0,
      "amount": 500000000,
      "status": "unspent",
      "type": "regular",
      "confirmations": 107,
      "block_height": 14100,
      "one_time_pub": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5",
      "commitment": "e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6"
    },
    {
      "txid": "c7f2e1d3a4b596870123456789abcdef0123456789abcdef0123456789abcdef",
      "output_index": 0,
      "amount": 72325093035,
      "status": "pending",
      "type": "coinbase",
      "confirmations": 7,
      "block_height": 14200,
      "one_time_pub": "b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9",
      "commitment": "f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7"
    }
  ]
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
POST/api/wallet/send

Builds a privacy-preserving transaction with one or more recipients using RingCT (Pedersen commitments, Bulletproofs range proofs, CLSAG ring signatures with fixed ring size 16), submits to mempool, and broadcasts via Dandelion++. Fee is calculated as max(min_fee, fee_per_byte * tx_size). Note: multi-recipient transactions link all recipients within the same transaction from a graph-analysis perspective. Requires unlocked full wallet (not view-only).

Parameters

Idempotency-Key header · string
Optional idempotency key for retry-safe client behavior. Duplicate requests with the same key and identical JSON payload replay the original response for a bounded window (~10 minutes). Reusing a key with a different payload fails with 409.

Request

{
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}
recipients required array
One or more recipients. Total amount must not exceed spendable balance.
dry_run boolean
If true, estimate the fee and return a preview without broadcasting. No transaction is created.

Response 200

{
  "txid": "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4",
  "fee": 15000,
  "change": 734850000,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}

Errors

400 { "error": "invalid address: checksum mismatch" }
400 { "error": "amount must be greater than 0" }
400 { "error": "insufficient spendable balance: have 250000000, need 500000000" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot send" }
500 { "error": "failed to build transaction: not enough ring members available" }
503 { "error": "no wallet loaded" }
POST/api/wallet/send/advanced

Builds a transaction with one or more recipients using caller-specified inputs (coin control). The caller selects which outputs to spend by referencing txid + output_index pairs from GET /api/wallet/outputs. No automatic input selection is performed. Supports dry_run mode to preview fee and change before broadcasting. Optional change_split (1-4) distributes change across multiple outputs for improved back-to-back send concurrency at the cost of reduced privacy. Same fee calculation, ring signatures, and Dandelion++ broadcast as the standard send endpoint.

Parameters

Idempotency-Key header · string
Optional idempotency key (same behavior as POST /api/wallet/send). Namespaced separately from the standard send endpoint.

Request

recipients required array
One or more recipients. The sum of specified inputs must cover total amount plus the computed fee.
inputs required array
Specific outputs to use as transaction inputs. No automatic selection is performed. Each output must be unspent, mature, unreserved, and not have its key image in the mempool.
dry_run boolean
If true, validate inputs and compute fee/change without building or broadcasting the transaction. The reserved inputs are released immediately.
change_split integer ≥ 1
Number of change outputs to create (1-4). Higher values improve back-to-back send concurrency at the cost of reduced privacy (deviates from the standard 2-output transaction fingerprint). Actual split count is clamped to min(change_split, change_amount_in_atomic_units).

Response 200 — Transaction broadcast

{
  "txid": "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
  "fee": 15000,
  "change": 234850000,
  "change_split": 1,
  "input_total": 735000000,
  "input_count": 2,
  "dry_run": false,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}

Response 200 — Dry-run preview

{
  "fee": 15000,
  "change": 234850000,
  "change_split": 1,
  "input_total": 735000000,
  "input_count": 1,
  "dry_run": true,
  "recipients": [
    {
      "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
      "amount": 500000000
    }
  ]
}

Errors

400 { "error": "inputs array is required for coin control" }
400 { "error": "input 0: output not found (txid abcd...1234, index 0)" }
400 { "error": "input 1: immature, has 3 confirmations, needs 10 (txid abcd...1234, index 0)" }
400 { "error": "input 0: key image already in mempool (txid abcd...1234, index 0)" }
400 { "error": "input 2: duplicate of input 0 (txid b3c4d5...a2b3c4, index 0)" }
400 { "error": "insufficient input amount: inputs total 100000, need 500015000 (amount 500000000 + fee 15000)" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot send" }
409 { "error": "idempotency key reuse with different request" }
409 { "error": "idempotency key in progress" }
429 { "error": "send rate limit exceeded" }
429 { "error": "send busy, retry later" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
POST/api/wallet/sign

Signs an arbitrary message with the wallet's spend private key (ed25519). Useful for proving address ownership to external services ('login with Blocknet'). Response includes `Cache-Control: no-store`. Requires unlocked full wallet (not view-only).

Request

{
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200"
}
message required string
The message to sign (max 1024 bytes)

Response 200

{
  "address": "9PNoFCqUa7K8e5JfV2Hs3TBt7kMzRGkPxJ4xVmn5cFbLd1Qy8W6uXo3Z9pNvRwA2hY7sK4mBjE6gC8dF",
  "signature": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "message": "blocknet-auth:1:blocknet.id:a1b2c3d4e5f6:1708531200"
}

Errors

400 { "error": "message is required" }
400 { "error": "message must be <= 1024 bytes" }
401 unauthorized
403 { "error": "wallet is locked" }
403 { "error": "view-only wallet cannot sign" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
POST/api/wallet/lock

Locks the wallet. All wallet endpoints (balance, address, history, send) will return 403 until unlocked. No request body required.

Response 200

{
  "locked": true
}

Errors

401 unauthorized
503 { "error": "no wallet loaded" }
POST/api/wallet/unlock

Unlocks the wallet with the wallet password. Uses constant-time comparison (`crypto/subtle.ConstantTimeCompare`) to prevent timing attacks.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password

Response 200

{
  "locked": false
}

Errors

400 { "error": "invalid JSON body" }
401 { "error": "incorrect password" }
503 { "error": "no wallet loaded" }
POST/api/wallet/seed

Returns the wallet's 12-word BIP39 recovery phrase. Requires Bearer token and wallet password confirmation. Response includes Cache-Control: no-store.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password

Response 200

{
  "mnemonic": "abandon ability able about above absent absorb abstract absurd abuse access accident",
  "words": [
    "abandon",
    "ability",
    "able",
    "about",
    "above",
    "absent",
    "absorb",
    "abstract",
    "absurd",
    "abuse",
    "access",
    "accident"
  ]
}

Errors

400 { "error": "invalid JSON body" }
401 { "error": "incorrect password" }
403 { "error": "wallet is locked" }
404 { "error": "no recovery seed available" }
503 { "error": "no wallet loaded" }
POST/api/wallet/sync

Rescans blocks from the wallet's last synced height to the chain tip, scanning for owned outputs and spent key images. Returns immediately if the wallet is already at the chain tip. Requires loaded, unlocked wallet.

Response 200 — Blocks scanned successfully

{
  "status": "synced",
  "synced_height": 14207,
  "chain_height": 14207,
  "blocks_scanned": 12,
  "outputs_found": 1,
  "outputs_spent": 0
}

Response 200 — Wallet already at chain tip

{
  "status": "already synced",
  "synced_height": 14207,
  "chain_height": 14207
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
500 { "error": "internal error" }
503 { "error": "no wallet loaded" }
POST/api/wallet/prove

Derives the deterministic transaction private key to prove a transaction was sent by this wallet. The tx key can be shared with a recipient or third party to verify payment. Works on both full and view-only wallets (uses the view private key). Requires loaded, unlocked wallet.

Request

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}
txid required string
Transaction hash (64 hex characters)

Response 200

{
  "txid": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "tx_key": "f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1"
}

Errors

400 { "error": "txid must be 64 hex characters" }
400 { "error": "coinbase transactions have no sender proof" }
401 unauthorized
403 { "error": "wallet is locked" }
404 { "error": "transaction not found on chain" }
409 { "error": "transaction was not sent by this wallet" }
500 { "error": "failed to derive tx key" }
503 { "error": "no wallet loaded" }
POST/api/wallet/audit

Scans all wallet outputs and groups them by key image. Outputs that share a key image are duplicates caused by a historical self-send key derivation bug — only one output per group can ever be spent, the rest are permanently unspendable (burned). No request body required. Requires loaded, unlocked wallet.

Response 200 — No burned funds detected

{
  "total_outputs": 42,
  "unique_key_images": 42,
  "failed_key_images": 0,
  "duplicate_groups": null,
  "total_burned": 0,
  "burned_outputs": 0
}

Response 200 — Duplicate key images found

{
  "total_outputs": 42,
  "unique_key_images": 40,
  "failed_key_images": 0,
  "duplicate_groups": [
    {
      "key_image": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
      "outputs": [
        {
          "txid": "b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4",
          "output_index": 0,
          "amount": 500000000,
          "spent": false,
          "block_height": 1200
        },
        {
          "txid": "c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5",
          "output_index": 1,
          "amount": 100000000,
          "spent": false,
          "block_height": 1201
        }
      ],
      "burned_amount": 100000000,
      "unspendable_count": 1
    }
  ],
  "total_burned": 100000000,
  "burned_outputs": 1
}

Errors

401 unauthorized
403 { "error": "wallet is locked" }
503 { "error": "no wallet loaded" }
POST/api/wallet/viewkeys

Returns the wallet's view-only key material (spend public key, view private key, view public key). These keys allow monitoring incoming funds and transaction history without spending ability. Requires Bearer token and wallet password confirmation. Response includes Cache-Control: no-store. Not available on view-only wallets.

Request

{
  "password": "my-wallet-passphrase"
}
password required string
Wallet password

Response 200

{
  "spend_pub": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
  "view_priv": "f0e1d2c3b4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e1",
  "view_pub": "d4c3b2a1e5f6d7c8b9a0e1f2d3c4b5a6e7f8d9c0b1a2e3f4d5c6b7a8e9f0d1c2"
}

Errors

400 { "error": "invalid JSON body" }
401 { "error": "incorrect password" }
403 { "error": "wallet is locked" }
403 { "error": "this is already a view-only wallet" }
429 { "error": "too many attempts; try again later" }
503 { "error": "no wallet loaded" }

# mining

Argon2id PoW miner control (~2GB RAM per thread)

GET/api/mining

Returns mining state, thread count, and stats (hashrate, hash count, blocks found, start time) when running. Hashrate and stat fields are omitted when the miner is stopped.

Response 200 — Miner is active

{
  "running": true,
  "threads": 4,
  "hashrate": 2.37,
  "hash_count": 8532,
  "blocks_found": 12,
  "started_at": "2026-02-06T14:00:00Z"
}

Response 200 — Miner is stopped

{
  "running": false,
  "threads": 4
}

Errors

401 unauthorized
POST/api/mining/start

Starts the Argon2id proof-of-work miner. Each thread allocates ~2GB RAM. No request body required.

Response 200

{
  "running": true
}

Errors

401 unauthorized
409 { "error": "mining already running" }
POST/api/mining/stop

Stops the miner. No request body required.

Response 200

{
  "running": false
}

Errors

401 unauthorized
409 { "error": "mining not running" }
POST/api/mining/threads

Sets the number of mining threads. Each thread uses ~2GB RAM for Argon2id. Takes effect immediately if the miner is running.

Request

{
  "threads": 4
}
threads required integer ≥ 1
Number of mining threads

Response 200

{
  "threads": 4
}

Errors

400 { "error": "threads must be >= 1" }
401 unauthorized
GET/api/mining/blocktemplate

Returns a complete block template ready for proof-of-work solving. Includes a pre-built coinbase transaction (paying to the loaded wallet by default, or to the optional `address` override), selected mempool transactions, and the computed merkle root. The `target` field (hex) is the PoW target that the Argon2id hash must be less than. The `header_base` field (hex) is the 92-byte serialized header without the nonce, used as input to the PoW hash function. The `template_id` field identifies this template and can be used with compact submit payloads (`template_id` + `nonce`) via `POST /api/mining/submitblock`. Requires a loaded wallet (for coinbase keys) but does not require it to be unlocked.

Parameters

address query · string
Optional reward address override. If provided, the coinbase output pays to this stealth address instead of the loaded wallet address.

Response 200

{
  "block": {
    "header": {
      "version": 1,
      "height": 14208,
      "prev_hash": "0000c3a5b7e2d1f489a1bc37e5d204f8c612aa9b33e7f04d12b8a9e6c7f50321",
      "merkle_root": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
      "timestamp": 1738800240,
      "difficulty": 100000,
      "nonce": 0
    },
    "transactions": []
  },
  "target": "000000000000f4240000000000000000000000000000000000000000000000000",
  "header_base": "0100000080370000...",
  "reward_address_used": "3wZc3y1Qw4eYpZtGJxkqkV6xG9c2oXHqfJjQ6b3sR5hYhJ8tZz1qvJ1mY1a9o7hV4pYwqH7xG5d5d8cJ8p4",
  "template_id": "7b3d2f4a9d11c8ef"
}

Errors

400 { "error": "invalid JSON body" }
401 unauthorized
503 { "error": "no wallet loaded" }
POST/api/mining/submitblock

Accepts a solved block (from pool mining or external miner) and adds it to the chain. The block must pass full validation: correct height, prev_hash linking to current tip, valid difficulty, valid Argon2id proof-of-work, valid merkle root, and valid transactions. On success the block is broadcast to all peers and subscribers are notified.

Request

Response 200

{
  "accepted": true,
  "hash": "0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4",
  "height": 14208
}

Errors

400 { "error": "invalid block: invalid proof of work" }
400 { "error": "invalid block: invalid height: expected 14208, got 14209" }
400 { "error": "block not accepted (duplicate or stale)" }
401 unauthorized

# events

Real-time Server-Sent Events stream for new blocks and mining notifications

GET/api/events

Server-Sent Events stream for real-time updates. On connection, emits a `connected` event with current chain state. Then streams `new_block` for every new block added to the chain and `mined_block` for blocks mined by this node. Sends SSE comment keepalives every 30s. Write timeout is disabled for this long-lived connection.

Event: connected

Sent immediately on connection

event: connected
data: {"chain_height":14207,"syncing":false}

Event: new_block

New block added to chain

event: new_block
data: {"height":14208,"hash":"0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4","timestamp":1738800240,"tx_count":3}

Event: mined_block

Block mined by this node

event: mined_block
data: {"height":14208,"hash":"0000d4b5a8e7c3f2910b6e5d4c3a2f1e0d9c8b7a6f5e4d3c2b1a09f8e7d6c5b4","reward":72325093035}

Event: keepalive

SSE comment sent every 30s to keep connection alive

: keepalive

Example

curl -N -H "Authorization: Bearer $(cat data/api.cookie)" http://127.0.0.1:8332/api/events

Errors

401 unauthorized

# admin

Dangerous administrative operations (data purge, etc.). These are irreversible and require password confirmation.

POST/api/purge

Deletes all blockchain data from disk. Requires password verification and explicit confirmation. Stops the daemon, removes the data directory, then shuts down the API server. A restart is required after this operation. This is irreversible.

Request

{
  "password": "my-wallet-passphrase",
  "confirm": true
}
password required string
Wallet password for verification
confirm required boolean
Must be true to confirm the destructive operation

Response 200

{
  "success": true,
  "message": "blockchain data purged successfully, restart required"
}

Errors

400 { "error": "confirmation required (set confirm: true)" }
401 { "error": "incorrect password" }
500 { "error": "failed to purge blockchain data: permission denied" }

# common errors

401 Missing or invalid `Authorization: Bearer <token>` header unauthorized
400 Invalid request parameters or malformed JSON { "error": "invalid JSON body" }
404 Requested resource does not exist { "error": "block not found" }
403 Wallet is locked — call `POST /api/wallet/unlock` first { "error": "wallet is locked" }
503 No wallet loaded on this node. In `--daemon` mode, call `POST /api/wallet/load` first. { "error": "no wallet loaded" }

# OpenAPI spec

Machine-readable OpenAPI 3.1 spec available at:
api_openapi.json