Skip to content

REST API

Alessandro Siniscalchi edited this page Feb 2, 2026 · 10 revisions

REST API

Overview

  • The REST service starts when you run brc721 with no subcommand (daemon mode). Wallet and tx subcommands do not start the API.
  • By default the server binds to 127.0.0.1:8083. Override it with --api-listen <host:port> or by setting BRC721_API_LISTEN. Bind only to trusted interfaces unless you have a reverse proxy or firewall in front of it.
  • The API is implemented with Axum and currently exposes read-only endpoints for node health, sync progress, collections, ownership, and address assets.
  • Token IDs and slot range values are serialized as strings to avoid JSON integer precision loss.

Authentication

  • There is no built-in authentication yet. The BRC721_API_TOKEN environment variable is reserved for a future release but is not enforced in the current build.
  • Until token support lands, restrict access by:
    • Binding to 127.0.0.1 (default) and tunneling through SSH, Tailscale, or a reverse proxy.
    • Placing the service behind an HTTPS gateway that enforces its own authentication layer.
    • Avoiding public exposure of the port entirely.

Endpoints

GET /health

  • Returns a plain status payload so you can wire the service into probes.
  • Response 200 OK:
    {
      "status": "ok",
      "uptimeSecs": 42
    }
    uptimeSecs counts seconds since the REST process started.

GET /state

  • Reports the latest block the scanner persisted. If no blocks have been processed yet, last is null.
  • Responses:
    • 200 OK with the payload shown below.
    • 500 Internal Server Error if the underlying storage layer (SQLite) fails.
  • Example response:
    {
      "last": {
        "height": 923580,
        "hash": "0000000000000000000example"
      }
    }

GET /collections

  • Lists every known collection, sorted by collection id.
  • Responses:
    • 200 OK with the payload shown below.
    • 500 Internal Server Error if the underlying storage layer (SQLite) fails.
  • Example response:
    {
      "collections": [
        {
          "id": "850123:0",
          "height": 850123,
          "txIndex": 0,
          "evmCollectionAddress": "0x1234abcd00000000000000000000000000000000",
          "rebaseable": true
        }
      ]
    }
    The id format is <height>:<txIndex>, matching the scanner's internal key.

GET /collections/{id}

  • Fetches a single collection by its <height>:<txIndex> identifier.
  • Responses:
    • 200 OK with the same body shape as entries in /collections.
    • 400 Bad Request if the id cannot be parsed (wrong delimiter, non-numeric height, etc.).
    • 404 Not Found if the id is well-formed but unknown.
    • 500 Internal Server Error if the underlying storage layer (SQLite) fails.

GET /collections/{collection_id}/tokens/{token_id}

  • Returns the current ownership status for a specific token that belongs to a registered Bitcoin collection.
  • collection_id follows the <height>:<txIndex> format.
  • token_id must be a 256-bit TokenID expressed as a base-10 string.
  • Responses:
    • 200 OK with either an initial owner or registered owner payload.
    • 400 Bad Request if the collection id or token id is malformed.
    • 404 Not Found when the collection does not exist in storage.
    • 500 Internal Server Error if storage lookups fail.
  • utxoHeight and utxoTxIndex point to the block/tx that created the current ownership UTXO (not the original NFT mint).
  • owner is the address derived from the output script when available.

Example response for the initial-owner case:

{
  "collectionId": "850123:0",
  "height": 850123,
  "txIndex": 0,
  "tokenId": "1043755606923009056510026266671580063926660113753943375567",
  "ownershipStatus": "INITIAL_OWNER",
  "ownerH160": "0x00112233445566778899aabbccddeeff00112233"
}

Example response for the registered-owner case:

{
  "collectionId": "850123:0",
  "height": 850123,
  "txIndex": 0,
  "tokenId": "1043755606923009056510026266671580063926660113753943375567",
  "ownershipStatus": "REGISTERED_OWNER",
  "ownerH160": "0xdeadbeef00112233445566778899aabbccddeeff",
  "owner": "bcrt1q...",
  "txid": "0000000000000000000000000000000000000000000000000000000000000000",
  "vout": 1,
  "utxoHeight": 840001,
  "utxoTxIndex": 2
}

GET /addresses/{address}/assets

  • Returns the currently indexed BRC-721 ownership ranges for a single Bitcoin address.
  • The server derives ownerH160 as hash160(scriptPubKey) for the address and returns the unspent ownership UTXOs associated with that script hash.
  • Responses:
    • 200 OK with the payload shown below.
    • 400 Bad Request if the address is malformed or does not match the daemon network.
    • 500 Internal Server Error if the underlying storage layer (SQLite) fails.
  • Example response:
    {
      "address": "bcrt1q...",
      "ownerH160": "0x00112233445566778899aabbccddeeff00112233",
      "utxos": [
        {
          "collectionId": "850123:0",
          "txid": "0000000000000000000000000000000000000000000000000000000000000000",
          "vout": 1,
          "initOwnerH160": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "utxoHeight": 840001,
          "utxoTxIndex": 2,
          "slotRanges": [
            { "start": "0", "end": "9" },
            { "start": "42", "end": "42" }
          ]
        }
      ]
    }

GET /utxos/{txid}/{vout}/assets

  • Returns all indexed BRC-721 ownership ranges present in a specific outpoint, along with the owner and UTXO creation height/tx index.
  • owner is the address derived from the output script when available.
  • Responses:
    • 200 OK with the payload shown below.
    • 400 Bad Request if the txid or vout is malformed.
    • 404 Not Found if the outpoint has no unspent ownership UTXOs.
    • 500 Internal Server Error if the underlying storage layer (SQLite) fails.
  • Example response:
    {
      "txid": "0000000000000000000000000000000000000000000000000000000000000000",
      "vout": 1,
      "ownerH160": "0xdeadbeef00112233445566778899aabbccddeeff",
      "owner": "bcrt1q...",
      "utxoHeight": 840001,
      "utxoTxIndex": 2,
      "assets": [
        {
          "collectionId": "850123:0",
          "initOwnerH160": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
          "slotRanges": [
            { "start": "0", "end": "9" },
            { "start": "42", "end": "42" }
          ]
        }
      ]
    }

Error Responses

  • All 4xx/5xx responses include a JSON payload with a short, human-readable message:
    { "message": "invalid address" }
  • Unknown endpoints return 404 with:
    { "message": "endpoint not found" }
    Check your logs for more context; the server logs validation errors at warn and storage failures at error.
  • When you receive a 500, retry with exponential backoff. The storage call may have raced with a reset or compaction, and simply waiting a second before the next request usually suffices.

Versioning and Stability

  • The REST API is still experimental. Expect occasional breaking changes in field names or payload structure until the project declares a stable release.
  • Breaking changes will be noted in the release notes and in this document. Pin your automation to a specific binary version if you require stability.

Usage Examples

# Health probe
curl -s http://127.0.0.1:8083/health | jq

# Check scanner progress
curl -s http://127.0.0.1:8083/state

# List every collection
curl -s http://127.0.0.1:8083/collections | jq '.collections | length'

# Fetch a specific collection by height:txIndex
curl -s http://127.0.0.1:8083/collections/850123:0

# Fetch BRC-721 assets for a Bitcoin address
curl -s http://127.0.0.1:8083/addresses/bcrt1q.../assets | jq

# Fetch BRC-721 assets for a specific outpoint
curl -s http://127.0.0.1:8083/utxos/<txid>/<vout>/assets | jq

Clone this wiki locally