| name | hathor-wallet-headless |
| description | Interact with the Hathor wallet-headless HTTP API — create and manage wallets, query balances, send transactions, create tokens and NFTs, run nano contracts, atomic swaps, multisig, and more. Use when the user mentions the headless wallet, wallet-headless, hathor wallet API, or wants to programmatically manage Hathor wallets via curl/HTTP. |
Hathor Wallet Headless
The wallet-headless is the official headless wallet of Hathor Network — a daemon that exposes wallet operations via HTTP. It runs a Hathor wallet in memory and lets any HTTP client create wallets, send transactions, interact with tokens and nano contracts, etc.
Default local port: 8000. Base URL in this doc: $HEADLESS (export to your URL, e.g. http://127.0.0.1:8000).
Core Concepts
The x-wallet-id header
Almost every /wallet/* endpoint requires an x-wallet-id header. That header routes the request to a specific wallet that was previously started with POST /start. Without it, the middleware rejects the request.
curl -H "x-wallet-id: my-wallet" "$HEADLESS/wallet/balance"
The root endpoints (/start, /multisig-pubkey, /push-tx, /configuration-string, /health) do not require it.
Optional API key
If the server was configured with http_api_key, add X-API-KEY: <key> to every request. This is a global gate, applied to all endpoints including the root ones.
Amounts are integers in cents
Every value / amount field is an integer in the smallest unit (cents). 1 HTR = 100. The API rejects floats. Convert user-facing amounts before calling.
Wallet readiness: statusCode: 3
After POST /start, the wallet takes time to sync. Poll GET /wallet/status:
0 = Closed
1 = Connecting
2 = Syncing
3 = Ready ← only now can you send transactions
Do not attempt send/balance queries before 3.
Tokens
- HTR (native token) uid is
"00".
- Custom tokens are referenced by their creation-tx hash (a hex string).
Quick Reference: Most Common Flow
export HEADLESS=http://127.0.0.1:8000
WALLET=my-wallet
curl -X POST "$HEADLESS/start" \
-H "Content-Type: application/json" \
-d "{\"wallet-id\":\"$WALLET\",\"seedKey\":\"default\"}"
while true; do
code=$(curl -s -H "x-wallet-id: $WALLET" "$HEADLESS/wallet/status" | jq -r '.statusCode')
[ "$code" = "3" ] && break
sleep 2
done
curl -H "x-wallet-id: $WALLET" "$HEADLESS/wallet/address"
curl -H "x-wallet-id: $WALLET" "$HEADLESS/wallet/balance"
curl -X POST "$HEADLESS/wallet/simple-send-tx" \
-H "x-wallet-id: $WALLET" \
-H "Content-Type: application/json" \
-d '{"address":"HNnK9...","value":123}'
curl -X POST "$HEADLESS/wallet/stop" -H "x-wallet-id: $WALLET"
Endpoint Map
| Area | Highlights | Reference |
|---|
| Lifecycle & core wallet | /start, /wallet/status, /wallet/balance, /wallet/address, /wallet/addresses, /wallet/simple-send-tx, /wallet/send-tx, /wallet/stop, history, decode | endpoints-core.md |
| Tokens, NFTs, UTXOs | /wallet/create-token, /wallet/mint-tokens, /wallet/melt-tokens, /wallet/create-nft, /wallet/utxo-filter, /wallet/utxo-consolidation | endpoints-tokens.md |
| Nano contracts | /wallet/nano-contracts/create, /execute, /state, /history, /create-on-chain-blueprint, oracle helpers | endpoints-nano.md |
| Offline signing & P2SH | /wallet/tx-proposal/*, /wallet/p2sh/tx-proposal/*, /push-tx | endpoints-tx-proposal.md |
| Atomic swap | /wallet/atomic-swap/tx-proposal/* | endpoints-atomic-swap.md |
| Tx templates & per-wallet config | /wallet/tx-template/*, /wallet/config/* | endpoints-misc.md |
| Custody integrations (enterprise) | /hsm/start, /fireblocks/start — only use if the user has told you they're configured | endpoints-misc.md |
| End-to-end recipes | Create wallet, fund, send, create token, mint, NFT, nano contract, swap | examples.md |
Error Shapes
Error responses are not uniform. Always check success: false first, then pick the right field:
| Source | Shape | Example |
|---|
Middleware (missing x-wallet-id, wallet not found) | { success, message, statusMessage } | {"success":false,"message":"Header 'X-Wallet-Id' is required.","statusMessage":""} |
Wallet lifecycle (errorCode like WALLET_ALREADY_STARTED) | { success, message, errorCode } | {"success":false,"message":"Failed to start wallet with wallet id wtest","errorCode":"WALLET_ALREADY_STARTED"} |
| Business-logic errors (insufficient funds, invalid tx, etc.) | { success, error } where error is a string | {"success":false,"error":"Token: 00. Insufficient amount of tokens to fill the amount."} |
| Schema validation (bad body / query) | { success, error } where error is an array of field errors | {"success":false,"error":[{"msg":"Invalid value","param":"address","location":"body"}]} |
When parsing, prefer: .message ?? (typeof .error === "string" ? .error : .error[0].msg).
Starting a Wallet: Your Options
POST /start has several modes. Pick one:
| Mode | Required fields | Use when |
|---|
| Seed from config | wallet-id, seedKey | Normal case — seed is in config.js keyed by name (e.g. default) |
| Seed inline | wallet-id, seed (24 words) | Testing; avoid in production (leaves seed in logs) |
| Read-only (xpub) | wallet-id, xpubkey | Watch-only wallet — can query, cannot sign |
| MultiSig | wallet-id, multisig: true, multisigKey | Multisig wallet, participants defined in config |
Enterprise custody modes (POST /hsm/start, POST /fireblocks/start) exist but are out of scope by default — only suggest them if the user has explicitly told you they have an HSM or Fireblocks configured.
Optional tuning: passphrase, gapLimit, scanPolicy (gap-limit default, or index-limit), policyStartIndex, policyEndIndex, historySyncMode (polling_http_api default, xpub_stream_ws, manual_stream_ws).
Gotchas Checklist
Security
- Never log seeds. When using
seed directly on /start, ensure logs are scrubbed.
- Protect the headless endpoint — anyone who can hit
/wallet/* owns the wallet. Use http_api_key, network ACLs, and/or a reverse proxy.
- Starting a wallet in read-only mode (
xpubkey) is safer for monitoring use-cases.