HTTP / JSON API
walletrs exposes the same RPC handler set as the gRPC surface over HTTP/JSON. Routes are generated at build time from (google.api.http) annotations on proto/walletrpc.proto — adding a new RPC is a one-line proto annotation, no Rust glue. Use this surface from browsers, curl, scripts, polyglot clients, or anything that prefers HTTP over protoc tooling.
The HTTP gateway listens on WALLETRS_HOST:WALLETRS_HTTP_PORT (default 127.0.0.1:8080). Bearer-token auth is shared with the gRPC surface — see Authentication.
Calling convention
Section titled “Calling convention”Every RPC is reachable as:
- Method:
POST - Path:
/wallet/<snake_case_method>(see the mapping table below) - Headers:
Authorization: Bearer <token>(except for/wallet/ping)Content-Type: application/json
- Body: JSON encoding of the request message
- Response: JSON encoding of the response message, or an error envelope (see error mapping)
Example:
TOKEN=...
# Liveness — no auth requiredcurl -sS -X POST http://127.0.0.1:8080/wallet/ping \ -H 'content-type: application/json' \ -d '{}'
# Authenticated callcurl -sS -X POST http://127.0.0.1:8080/wallet/create_system_managed_key \ -H "authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"user_id":"alice","device_id":"alice-device-1","key_name":"primary"}'JSON encoding rules
Section titled “JSON encoding rules”The gateway uses pbjson for prost ↔ JSON conversion, which implements proto3 JSON semantics:
- Numeric fields stay numeric (no JSON-string coercion).
uint64etc. are emitted as numbers — note that JS clients larger than2^53should treat largeuint64asstring-typed in their schema. bytesfields are base64-encoded strings.- Enums use the canonical name as a string (e.g.
"SCRIPT_TYPE_TAPROOT"), not the integer value. - Field names use camelCase or the proto’s snake_case — both are accepted on input. Output is camelCase by default.
- Empty messages serialize as
{}.
For request messages with no fields (e.g. PingRequest), pass an empty JSON object: {}.
RPC ↔ HTTP path mapping
Section titled “RPC ↔ HTTP path mapping”| HTTP path | gRPC method | Purpose |
|---|---|---|
POST /wallet/ping | Ping | Liveness probe; bypasses auth. |
POST /wallet/create_generic_wallet | CreateGenericWallet | Compile spending conditions into a Bitcoin wallet (single-sig, multisig, or taproot with recovery leaves). |
POST /wallet/create_system_managed_key | CreateSystemManagedKey | Generate a new key inside walletrs; private material stored envelope-encrypted under WALLETRS_KEK. |
POST /wallet/create_customer_managed_key | CreateCustomerManagedKey | Register a customer-supplied xpub (private key never leaves the client). |
POST /wallet/get_managed_key | GetManagedKey | Fetch a managed key by (user_id, device_id, key_type). |
POST /wallet/list_managed_keys | ListManagedKeys | List managed keys, optionally filtered by user or type. |
POST /wallet/get | GetWallet | Read wallet metadata + balance. |
POST /wallet/update | UpdateWallet | Sync chain state from electrs. |
POST /wallet/reveal_next_address | RevealNextAddress | Reveal num unused addresses (external by default; pass change: true for change addrs). |
POST /wallet/list_addresses | ListAddresses | List all revealed addresses. |
POST /wallet/get_transactions | GetWalletTransactions | Wallet’s transaction history. |
POST /wallet/get_utxos | GetWalletUtxos | Wallet’s spendable UTXOs. |
POST /wallet/fund_transaction | FundWalletTransaction | Build an unsigned PSBT to a destination. For taproot, set selected_leaf_hash to pick a spending path. |
POST /wallet/sign_transaction | SignWalletTransaction | In-process sign with a system-managed key. |
POST /wallet/add_verify_transaction_signature | AddVerifyTransactionSignature | Combine an externally-signed PSBT into the in-flight one (hardware-wallet flow). |
POST /wallet/finalize_transaction | FinalizeWalletTransaction | Finalize a fully-signed PSBT. |
POST /wallet/broadcast_transaction | BroadcastWalletTransaction | Broadcast the finalized tx through electrs. |
POST /wallet/get_spending_paths | GetWalletSpendingPaths | List available taproot spending paths for a wallet. |
The mapping is generated from (google.api.http) annotations in proto/walletrpc.proto, so this table mirrors the proto exactly.
Error mapping
Section titled “Error mapping”Handler errors are emitted as tonic::Status and mapped to standard HTTP status codes. The body is always { "code": <int>, "message": <string> }, where code is the gRPC status code as an integer.
| gRPC code | HTTP status | Body field code |
|---|---|---|
OK | 200 OK | 0 |
INVALID_ARGUMENT, OUT_OF_RANGE, FAILED_PRECONDITION | 400 Bad Request | 3, 11, 9 |
UNAUTHENTICATED | 401 Unauthorized | 16 |
PERMISSION_DENIED | 403 Forbidden | 7 |
NOT_FOUND | 404 Not Found | 5 |
ALREADY_EXISTS, ABORTED | 409 Conflict | 6, 10 |
RESOURCE_EXHAUSTED | 429 Too Many Requests | 8 |
CANCELLED | 499 | 1 |
INTERNAL, UNKNOWN, DATA_LOSS | 500 Internal Server Error | 13, 2, 15 |
UNIMPLEMENTED | 501 Not Implemented | 12 |
UNAVAILABLE | 503 Service Unavailable | 14 |
DEADLINE_EXCEEDED | 504 Gateway Timeout | 4 |
Example:
$ curl -sS -i -X POST http://127.0.0.1:8080/wallet/get \ -H "authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"wallet_id":"never-existed"}'
HTTP/1.1 404 Not Foundcontent-type: application/json
{ "code": 5, "message": "Wallet not found" }Smoke tests
Section titled “Smoke tests”A minimal end-to-end script (regtest assumed):
TOKEN=$(grep 'STORE THIS' walletrs.log | tail -1 | awk '{print $NF}')
curl -sS -X POST http://127.0.0.1:8080/wallet/ping \ -H 'content-type: application/json' -d '{}'
curl -sS -X POST http://127.0.0.1:8080/wallet/create_system_managed_key \ -H "authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{"user_id":"alice","device_id":"alice-hot","key_name":"primary"}'
curl -sS -X POST http://127.0.0.1:8080/wallet/create_generic_wallet \ -H "authorization: Bearer $TOKEN" \ -H 'content-type: application/json' \ -d '{ "user_id": "alice", "wallet_id": "alice-wallet-1", "network": "regtest", "preferred_script_type": "SCRIPT_TYPE_AUTO", "spending_conditions": [{ "id": "primary", "is_primary": true, "timelock": 0, "threshold": 1, "policy": "SINGLE", "managed_key_ids": ["alice-hot"] }] }'For a fuller worked flow, see the end-to-end guide.
Compatibility
Section titled “Compatibility”The HTTP routes are generated from the proto, so HTTP and gRPC always agree on the request / response shape. Adding a new RPC requires only the (google.api.http) annotation on the proto plus the usual handler addition; the HTTP route appears automatically on the next build.