Self-Custody Wallet

Get network parameters

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/config

Returns the parameters a client needs to work with BTC locally: network (mainnet / testnet), nativeSymbol and nativeDecimals, the default derivation path, the dust threshold in satoshis, and explorer URL templates for addresses and transactions (both contain {value} for the client to substitute). These describe the INSTALLATION, one deployment serves exactly one network, so there is no chain or token registry to query.

Authorization: walletAuth

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/config
cURL
curl -X POST "$BASE_URL/selfcustody/config" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "network": "mainnet",
      "nativeSymbol": "string",
      "nativeDecimals": 0,
      "defaultDerivationPath": "string",
      "dustThresholdSats": "string",
      "explorerAddressUrlTemplate": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
      "explorerTxUrlTemplate": "string"
    }
  }
}

Register wallet

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/wallet/register

Creates a self-custody wallet for the public key in the JWT. The private key never reaches the server. One public key maps to at most one wallet: calling this again returns the existing walletId with created: false and does NOT overwrite name. A freshly registered wallet has no addresses, add them with /selfcustody/address/add.

Authorization: walletAuth

Body required

application/json
namestringrequired
Example:Satoshi Coffee

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/wallet/register
cURL
curl -X POST "$BASE_URL/selfcustody/wallet/register" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "name": "Satoshi Coffee"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "walletId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "created": true
    }
  }
}

Get wallet

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/wallet/get

Returns the wallet of the authenticated public key with its addresses, each carrying the derivation path and index the client used. An empty addresses list is a valid state, not an error. monitoredAt: null signals an address that never reached the indexer, balances and history for it would be empty, so it is surfaced rather than hidden.

Authorization: walletAuth

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/wallet/get
cURL
curl -X POST "$BASE_URL/selfcustody/wallet/get" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Satoshi Coffee",
      "createdAt": "string",
      "addresses": [
        {
          "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "pubkeyHex": "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc",
          "derivationPath": "string",
          "addressIndex": 0,
          "monitoredAt": "string"
        }
      ]
    }
  }
}

Get balances

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/wallet/get-balances

Returns per-address balances split by availability, all in satoshis: spendableSats (confirmed and unlocked, the only funds that can be spent now), unconfirmedSats (outputs of transactions not yet in a block, including our own change), lockedSats (outputs reserved by an unfinished /selfcustody/transaction/build) and totalSats, which is the sum of the three. The buckets never overlap, so they can be displayed side by side. spendableInUsd is null when a rate is unavailable, that is not a request failure, the satoshi amounts are still exact. Pass addressId to narrow to one address.

Authorization: walletAuth

Body required

application/json
addressIdstring
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/wallet/get-balances
cURL
curl -X POST "$BASE_URL/selfcustody/wallet/get-balances" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "balances": [
        {
          "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "spendableSats": "string",
          "unconfirmedSats": "string",
          "lockedSats": "string",
          "totalSats": "string",
          "spendableInUsd": 0
        }
      ],
      "updatedAt": "string"
    }
  }
}

Resync with the blockchain

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/wallet/resync

Forces an out-of-schedule resync of the wallet's addresses. Pass addressId to resync one, or deep: true to re-read the whole history instead of only new blocks. Results are reported per address: a failure on one address comes back as ok: false with an error rather than failing the whole request, so a partial refresh is never mistaken for a complete one.

Authorization: walletAuth

Body required

application/json
addressIdstring
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
deepboolean
Example:true

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/wallet/resync
cURL
curl -X POST "$BASE_URL/selfcustody/wallet/resync" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "deep": true
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "results": [
        {
          "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "ok": true,
          "error": "string",
          "syncedTillBlock": 0,
          "ingestedTxCount": 0
        }
      ]
    }
  }
}

Add address

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/address/add

Registers an address the CLIENT derived and starts tracking it on-chain. The server verifies that address really derives from pubkeyHex and rejects the request otherwise, without that check a wallet could claim someone else's address. The call is idempotent (created: false on repeat) and only succeeds once the address is actually being tracked: an untracked address would silently report a zero balance.

Authorization: walletAuth

Body required

application/json
addressstringrequired
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
pubkeyHexstringrequired
Example:02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc
derivationPathstringrequired
Example:string
addressIndexintegerrequired
Example:0
ownershipChallengeIdstring
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
ownershipSignaturestring
Example:4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7a…

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/address/add
cURL
curl -X POST "$BASE_URL/selfcustody/address/add" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "pubkeyHex": "02a1633cafcc01ebfb6d78e39f687a1f0995c62fc95f51ead10a02ee0be551b5dc",
  "derivationPath": "string",
  "addressIndex": 0,
  "ownershipChallengeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "ownershipSignature": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
      "address": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
      "created": true,
      "monitoredAt": "string",
      "wasAlreadyMonitored": true
    }
  }
}

Estimate fee

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/fee/estimate

Returns recommended fee rates as three tiers (slow / standard / fast) in sat/vByte, sourced from a mempool estimator, with updatedAt telling how fresh they are, a cached rate is served when the source is unreachable, and its age is shown rather than hidden. Pass addressId AND amountSats together to also get exact: the fee for that address's real UTXO set, the inputCount and vBytes behind it, recipientAmountSats, and maxSendableSats. The last one is what a “Max” button should send, for BTC fee = feeRate × vBytes and vBytes depends on the input count, which only the server knows.

Authorization: walletAuth

Body required

application/json
addressIdstring
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
amountSatsstring | number

Amount in satoshis.

feeIncludedboolean
Example:true

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/fee/estimate
cURL
curl -X POST "$BASE_URL/selfcustody/fee/estimate" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "amountSats": "string",
  "feeIncluded": true
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "slow": {
        "feeRateSatVb": 50000,
        "estimatedSecs": 0,
        "exact": {
          "feeSats": null,
          "inputCount": 0,
          "vBytes": 0,
          "recipientAmountSats": null,
          "maxSendableSats": null,
          "sufficientFunds": true
        }
      },
      "standard": {
        "feeRateSatVb": 50000,
        "estimatedSecs": 0,
        "exact": {
          "feeSats": null,
          "inputCount": 0,
          "vBytes": 0,
          "recipientAmountSats": null,
          "maxSendableSats": null,
          "sufficientFunds": true
        }
      },
      "fast": {
        "feeRateSatVb": 50000,
        "estimatedSecs": 0,
        "exact": {
          "feeSats": null,
          "inputCount": 0,
          "vBytes": 0,
          "recipientAmountSats": null,
          "maxSendableSats": null,
          "sufficientFunds": true
        }
      },
      "updatedAt": "string"
    }
  }
}

Build unsigned transaction

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/transaction/build

Builds an unsigned PSBT for the client to sign locally; the private key never reaches the server. Set feeIncluded: false to add the fee on top of amountSats, or true to take it out of that amount, recipientAmountSats reports what the recipient actually receives. Change below the dust threshold is added to the fee instead of creating an unspendable output. The selected outputs are LOCKED until expiresAt, so a concurrent build cannot pick the same ones; quoteId identifies both the quote and that lock.

Authorization: walletAuth

Body required

application/json
addressIdstringrequired
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
toAddressstringrequired

Destination Bitcoin address for the funds.

Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
amountSatsstring | numberrequired

Amount in satoshis.

feeRateSatVbnumberrequired

Fee rate in satoshis per virtual byte (sat/vB).

Example:50000
feeIncludedbooleanrequired
Example:true

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/transaction/build
cURL
curl -X POST "$BASE_URL/selfcustody/transaction/build" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "toAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "amountSats": "string",
  "feeRateSatVb": 50000,
  "feeIncluded": true
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "quoteId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "psbtBase64": "cHNidP8BAHECAAAAAfYI0kF1pQ0i3xL0AAAAAAD9////AsBdAQAAAAAAFgAUdR5",
      "feeSats": "string",
      "recipientAmountSats": "string",
      "changeSats": "string",
      "inputCount": 0,
      "vBytes": 0,
      "expiresAt": "string"
    }
  }
}

Broadcast signed transaction

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/transaction/broadcast

Submits the transaction the client signed and finalized, rawTx is signed transaction hex, not a PSBT, and releases the lock on the outputs it spent. Only an unexpired quoteId is accepted; past expiresAt the quote must be rebuilt. Retrying with the same rawTx is safe and returns wasAlreadySubmitted: true: the network may have accepted the first attempt while the response never reached the client.

Authorization: walletAuth

Body required

application/json
quoteIdstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
rawTxstringrequired
Example:string

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/transaction/broadcast
cURL
curl -X POST "$BASE_URL/selfcustody/transaction/broadcast" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "quoteId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "rawTx": "string"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
      "wasAlreadySubmitted": true
    }
  }
}

List transactions

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/transaction/list

Returns transaction history newest-first with a nextCursor for the next page (null on the last one); transactions not yet in a block come first. Amounts are stated from the wallet's side: for an incoming transaction what arrived, for an outgoing one what went to recipients excluding our own change. feeSats is set only on outgoing transactions, the spender pays the fee, and may be null when not all inputs are known to the indexer, since reporting zero there would be wrong. Filter with statuses and directions.

Authorization: walletAuth

Body required

application/json
addressIdstring
Example:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq
cursorstring
Example:string
limitinteger
Example:20
statusesenum[]
directionsenum[]

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/transaction/list
cURL
curl -X POST "$BASE_URL/selfcustody/transaction/list" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
  "cursor": "string",
  "limit": 20,
  "statuses": [
    "pending"
  ],
  "directions": [
    "in"
  ]
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "txs": [
        {
          "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
          "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
          "direction": "in",
          "status": "pending",
          "amountSats": "string",
          "feeSats": null,
          "counterpartyAddresses": [
            "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
          ],
          "blockHeight": 845000,
          "blockTime": "string",
          "confirmations": 0
        }
      ],
      "nextCursor": "string"
    }
  }
}

Get transaction

POSThttps://api.gobtcpay.com/public/api/v1.2/selfcustody/transaction/get

Returns one transaction by txid, in the same shape as the list entries, including confirmations. Use it to follow a send you just broadcast without refetching the whole history, and so that a dropped websocket cannot leave a transfer stuck on “processing” forever.

Authorization: walletAuth

Body required

application/json
txidstringrequired

On-chain Bitcoin transaction id (hex).

Example:4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b

Responses

idstringrequired
Example:3fa85f64-5717-4562-b3fc-2c963f66afa6
resultobjectrequired
POST /selfcustody/transaction/get
cURL
curl -X POST "$BASE_URL/selfcustody/transaction/get" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
  "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"
}'
200 Successful response
JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "result": {
    "$case": "success",
    "success": {
      "tx": {
        "txid": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b",
        "addressId": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
        "direction": "in",
        "status": "pending",
        "amountSats": "string",
        "feeSats": "string",
        "counterpartyAddresses": [
          "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
        ],
        "blockHeight": 845000,
        "blockTime": "string",
        "confirmations": 0
      }
    }
  }
}