> ## Documentation Index
> Fetch the complete documentation index at: https://nexus.gerowallet.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from Maestro: Bitcoin

> Endpoint map and behavioural differences when moving Bitcoin reads and submits from Maestro to Nexus

Maestro's developer API retires on September 18, 2026, per their announcement of August 18, 2026:
every endpoint is retired and keys stop working on that date. This page maps the Maestro Bitcoin
endpoints most integrations depend on to their Nexus equivalents, and details the places where the
swap is not drop-in.

The base URL becomes `https://nexus.gerowallet.io` (Maestro's Bitcoin services sit under
`https://xbt-mainnet.gomaestro-api.org/v0`, with the Node RPC service at `/v0/rpc`), and the
credential becomes an `X-Api-Key: nxs_...` header carrying a `BITCOIN_MAINNET` key. See
[Authentication](/docs/authentication) for how keys are issued and scoped.

<Note>
  Nexus keys are bound to one chain and one network at creation time. A Bitcoin key is created as
  `BITCOIN_MAINNET` or `BITCOIN_TESTNET4`, the `network` query parameter is then optional, and
  sending a value that disagrees with the key returns **400 Network Mismatch**. If you also read
  Cardano, that is a separate key on the same account, not a separate integration. See
  [Migrate from Maestro: Cardano](/docs/maestro-migration-cardano) for that half.
</Note>

## Endpoint map

Maestro paths are shown relative to their `/v0` base, so `/rpc/...` is the Node RPC service and the
bare paths are the Blockchain Indexer and Mempool Monitoring services.

| Maestro                                        | Nexus                                                                 | Notes                                                                                                                                                                                                                                                                                                                                                                      |
| ---------------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /rpc/transaction/submit`                 | `POST /api/btc/txs/submit` with `{"rawTxHex": "..."}`                 | Drop-in. bitcoind reject codes map to 400, already-in-chain maps to 409                                                                                                                                                                                                                                                                                                    |
| `GET /addresses/{a}/utxos?from&to&cursor`      | `GET /api/btc/addresses/{a}/utxos/page?from&to&cursor&limit`          | Returns the envelope `{data, tip: {blockHeight, blockHash}, nextCursor}`. `tip.blockHeight` replaces `last_updated.block_height`. Unconfirmed UTXOs are always included with `status.confirmed=false`. The sibling `GET /api/btc/addresses/{a}/utxos` takes only `network` and returns the bare array; sending a paging parameter there is a 400 pointing at `/utxos/page` |
| per-UTXO `runes` and `inscriptions`            | `/utxos/page` plus `includeOrdinals=true`                             | Adds `scriptPubkey`, `ordinalsIndexed`, `inscriptions: string[]` and `runes: [{runeId, name, amount, divisibility, symbol}]` to each UTXO. `runeId` is `block:tx`. Rune amounts are raw units, divide by `10^divisibility`                                                                                                                                                 |
| `GET /rpc/transaction/{txid}?verbose=true`     | `GET /api/btc/txs/{txid}?includeHex=true`                             | bitcoind verbosity-2 shape: `vin[].prevout.scriptPubKey.address` and `.type` on confirmed transactions, `vin[].txinwitness` passed through unmodified, `hex` inline when `includeHex=true`. No `input_addresses` or `output_addresses` arrays. Monetary values are BTC-denominated, not satoshi strings                                                                    |
| `POST /rpc/transaction/batch?verbose=true`     | `POST /api/btc/txs/batch` with `{"txids": [...], "includeHex": true}` | Maximum 250 txids per call. Response order mirrors the request; unknown txids come back as `null` entries                                                                                                                                                                                                                                                                  |
| `GET /addresses/{a}/txs?from&cursor`           | `GET /api/btc/addresses/{a}/txs?from&to&cursor&limit`                 | Confirmed transaction summaries `{txid, blockHeight, blockHash, blockTime}`, descending by height, plus `tip` and `nextCursor`. `limit` defaults to 50, maximum 200                                                                                                                                                                                                        |
| `GET /addresses/{a}/inscriptions?count&cursor` | `GET /api/btc/addresses/{a}/inscriptions?cursor&limit`                | Entries are `{id, number, satpoint, txid, vout, satOffset, value, genesisHeight}`. `limit` replaces `count`, defaults to 25, maximum 100                                                                                                                                                                                                                                   |
| `GET /mempool/addresses/{a}/utxos`             | not needed                                                            | Covered by the unconfirmed-inclusion guarantee on the UTXO endpoint above. Filter on `status.confirmed` instead of making a second call                                                                                                                                                                                                                                    |

Beyond the map, Nexus serves a bare inscription-and-rune summary at
`GET /api/btc/addresses/{address}/ordinals`, plus blocks, chain tip, fee estimates, mempool state
and direct ordinals lookups. See the [API Reference](/docs/api-reference) for that surface.

## Differences that are not drop-in

Ten behavioural notes, listed exhaustively. Everything else in the map is a path-and-header change.

### 1. The watermark field is renamed

`last_updated.block_height` becomes `tip.blockHeight`. The value is fetched **before** the data
query, so it is never ahead of the rows you were handed. That makes it a safe scan watermark: on
the next poll you may re-scan a block, and you can never skip one. `tip.blockHash` is alongside it
for reorg detection.

### 2. BTC versus satoshi denominations

Transaction-level monetary values (`vout[].value`, `vin[].prevout.value`, `fee`) are BTC decimals,
where Maestro returned satoshi strings. UTXO `value` on the address endpoints stays in satoshis.
Two denominations, split by resource, so check every arithmetic site rather than applying one global
conversion.

### 3. Ordinals results carry an authority marker

Each UTXO returned with `includeOrdinals=true` carries `ordinalsIndexed`. When it is `true`, the
`inscriptions` and `runes` fields are authoritative and an empty list means provably none. When it
is `false`, ord has not examined that output yet (typically an unconfirmed output, or ord lagging
the tip) and both fields are **omitted rather than reported empty**.

Match assets only on `ordinalsIndexed=true` entries. Treating an omitted field as "no inscriptions"
will misclassify outputs that simply have not been indexed yet; they resolve on confirmation.

### 4. `runeId` is best-effort

On the rare failure to resolve a rune name, `runeId` is `null` for that one balance rather than
failing the whole request. Tolerate the null and retry on the next poll instead of treating it as a
hard error.

### 5. Per-UTXO ordinals field shapes changed

`runes[].runeId` replaces `rune_id`, and `inscriptions` is a flat array of inscription ID strings
where Maestro returned objects keyed by `inscription_id`.

### 6. Inscription listing fields are satpoint-parsed

On `GET /api/btc/addresses/{a}/inscriptions`, the fields `satOffset`, `vout` and `txid` replace
`utxo_sat_offset`, `utxo_vout` and `utxo_txid`. They are parsed out of the `satpoint`, which is also
returned verbatim. `utxo_block_height` and `utxo_confirmations` have no equivalent.

### 7. The mempool endpoint call can be deleted

`GET /mempool/addresses/{a}/utxos` has no Nexus counterpart because it does not need one:
unconfirmed UTXOs are always present in the main UTXO response with `status.confirmed=false`. Delete
the second call and filter client-side.

### 8. Mempool transactions have no prevout

A transaction fetched through `GET /api/btc/txs/{txid}` while it is still in the mempool returns
inputs without `prevout`. That is a bitcoind limitation (no undo data exists until the transaction
is in a block), not a Nexus choice. Any logic that identifies a counterparty from input addresses
has to run against confirmed transactions.

### 9. Duplicates are possible during a paged scan

If a UTXO confirms partway through a paged scan it can appear twice, once unconfirmed and once
confirmed. Key your upserts by `(txid, vout)` and the duplicate collapses.

### 10. Paged reads live on a separate route

The envelope comes from `GET /api/btc/addresses/{a}/utxos/page`. Its sibling
`GET /api/btc/addresses/{a}/utxos` accepts only `network` and always returns the bare array, and
sending `from`, `to`, `cursor`, `limit` or `includeOrdinals` to it returns **400** with a message
naming the parameters it received and pointing at `/utxos/page`. The failure is deliberate:
ignoring them would answer a paged request with the entire unpaged set.

On `/utxos/page`, passing `includeOrdinals=false` is exactly equivalent to omitting the parameter.
It does not change the response shape, which is the envelope either way.

## Worked examples

A UTXO page with ordinals, and a batch transaction lookup.

<CodeGroup>
  ```bash UTXOs theme={null}
  curl -X GET "https://nexus.gerowallet.io/api/btc/addresses/$ADDRESS/utxos/page?from=880000&limit=2&includeOrdinals=true" \
    -H "X-Api-Key: nxs_your_api_key_here"
  ```

  ```json UTXOs response theme={null}
  {
    "data": [
      {
        "txid": "9d1f...c07a",
        "vout": 0,
        "value": 546,
        "status": {
          "confirmed": true,
          "blockHeight": 880142,
          "blockHash": "0000...31bd",
          "blockTime": 1737412800
        },
        "ordinalsIndexed": true,
        "scriptPubkey": "5120...9e4f",
        "inscriptions": ["b3ac...ee01i0"],
        "runes": [
          {
            "runeId": "840000:3",
            "name": "DOGGOTOTHEMOON",
            "amount": "125000000",
            "divisibility": 5,
            "symbol": "🐕"
          }
        ]
      },
      {
        "txid": "4e77...11b2",
        "vout": 1,
        "value": 21500,
        "status": { "confirmed": false },
        "ordinalsIndexed": false
      }
    ],
    "tip": {
      "blockHeight": 880913,
      "blockHash": "0000...a6c2"
    },
    "nextCursor": "eyJoIjo4ODAxNDIsInQiOiI5ZDFm..."
  }
  ```

  ```bash Batch theme={null}
  curl -X POST "https://nexus.gerowallet.io/api/btc/txs/batch" \
    -H "X-Api-Key: nxs_your_api_key_here" \
    -H "Content-Type: application/json" \
    -d '{"txids": ["9d1f...c07a", "0000...0000"], "includeHex": false}'
  ```

  ```json Batch response theme={null}
  {
    "data": [
      {
        "txid": "9d1f...c07a",
        "vin": [
          {
            "txid": "77ab...4d10",
            "vout": 2,
            "txinwitness": ["3044...01", "0250...9c"],
            "prevout": {
              "value": 0.00042,
              "scriptPubKey": {
                "address": "bc1p...4kqe",
                "type": "witness_v1_taproot"
              }
            }
          }
        ],
        "vout": [
          {
            "value": 0.00000546,
            "n": 0,
            "scriptPubKey": { "address": "bc1p...9m2t", "type": "witness_v1_taproot" }
          }
        ],
        "fee": 0.00001204
      },
      null
    ]
  }
  ```
</CodeGroup>

The second element is `null` because the second txid is unknown. Order always mirrors the request,
so index alignment is safe.

<Note>
  Note the two denominations in one response: the UTXO `value` above is `546` satoshis, while the
  transaction `vout[].value` is `0.00000546` BTC. That is difference 2 in practice.
</Note>

## Why not the free one

For Bitcoin there is no Koios. The no-cost options a Maestro user weighs are a self-hosted
`bitcoind` plus an indexer such as electrs or ord, or the public mempool.space and blockstream.info
APIs, which publish no rate-limit guarantees and are run as a public good. Self-hosting gives full
control and costs an engineer's time to keep an ord index in sync; the public APIs cost nothing and
promise nothing. Nexus serves address UTxOs, ordinals, runes, fees, mempool and submit behind one
key with per-tier rate limits and the same account that covers Cardano and Midnight. If your
Bitcoin reads are light and you can tolerate an unannounced limit, the public APIs are a fine
answer; if they are production traffic, they are not.

## What to test first

Three checks that cover the differences most likely to bite.

1. **A tip watermark round trip.** Read a page from `/utxos/page`, store `tip.blockHeight`, then poll again with
   `from` set to the stored value. Confirm you see overlap rather than a gap, and that your upserts
   are keyed by `(txid, vout)` so a UTXO that confirmed between the two reads collapses instead of
   duplicating.
2. **A batch call containing one fake txid.** Send `POST /api/btc/txs/batch` with a real txid and 64
   hex zeros, and confirm your deserializer accepts a `null` entry in `data` without throwing and
   without shifting subsequent indices.
3. **`includeOrdinals=true` on a rune-holding address.** Confirm you read `runeId` in `block:tx`
   form, divide `amount` by `10^divisibility`, and skip any UTXO whose `ordinalsIndexed` is `false`
   instead of recording it as holding nothing. Include an address with unconfirmed inbound funds so
   at least one such UTXO appears.

## Next steps

<CardGroup cols={2}>
  <Card title="Migrate from Maestro: Cardano" icon="chart-network" href="/docs/maestro-migration-cardano">
    The Cardano half of the same migration
  </Card>

  <Card title="Authentication" icon="key" href="/docs/authentication">
    Key creation, scoping, and the `network` parameter
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/docs/concepts/rate-limits">
    Per-tier quotas and per-second limits
  </Card>

  <Card title="API Reference" icon="book" href="/docs/api-reference">
    The full Bitcoin surface, endpoint by endpoint
  </Card>
</CardGroup>
