REST Workflows
REST endpoints for workflows
Workflows are declarative sequences of steps executed asynchronously. Results are cached in RocksDB and can be queried multiple times without re-execution.
Execution model:
- Submitted via
POST /workflows, queued immediately - Execute asynchronously — microseconds to minutes depending on steps
- Results cached until explicitly deleted
- Query results via
/pubkeys-set/txns,/pubkeys-set/utxos, or/workflows/{id}/results/{type}
GET /workflows
Section titled “GET /workflows”Returns workflows in pending and running states by default — suitable for dashboards and operational monitoring. Finished workflows are retained briefly up to TTL seconds and can be included. See below.
To track completion of a specific workflow use GET /workflows/{id}, or subscribe to workflow.progress events and listen for state: completed.
GET /workflowsAdd all finished history or select as desired:
GET /workflows?include=finished // all finished historyGET /workflows?include=completedGET /workflows?include=cancelledGET /workflows?include=failedResponse:
[ { "id": "VdncVtkNg5", "request_id": "256483", "name": "Import wallet-1", "state": "running", "total_steps": 6, "elapsed_ms": 12, "current_step_status": { "step": 3, "progress": 45.0 }, "steps": [ {"step": "pubkeys.extpubkey.import"}, {"step": "pubkeys.scripthash.subscribe"}, {"step": "pubkeys.scripthash.history"}, {"step": "pubkeys.transaction.fetch"}, {"step": "blockheaders.unresolved.fetch"}, {"step": "pubkeys.unspent.compute"} ] }]Response fields:
| Field | Description |
|---|---|
id | Workflow ID |
request_id | Echoed from workflow submission. Absent if not set |
name | Echoed from workflow submission. Absent if not set |
state | Workflow state — See table below. |
total_steps | Total number of steps in the workflow |
current_step_status | Per-step detail — see below. Present only when state is running |
pending_ms | Milliseconds the workflow has been waiting in the queue. Present only when state is pending |
elapsed_ms | Milliseconds the workflow has been executing. Present only when state is running |
steps | Array of step objects, each containing a step name as listed in the Step Reference. Present only when state is running or pending |
Workflow states:
| State | Description |
|---|---|
pending | Queued, not yet executing |
running | Currently executing |
completed | Finished successfully |
cancelled | Stopped before completion |
failed | Terminated with an error |
current_step_status fields:
| Field | Description |
|---|---|
step | 1-based index of the currently executing step |
progress | Percent complete within the current step (0–100) |
The current_step_status object appears in both REST responses and workflow.progress
WebSocket events. See workflow.progress for
the event schema.
step fields:
| Field | Description |
|---|---|
step | 1-based position of this step in the workflow |
name | Step name as listed in the Step Reference |
GET /workflows/{id}
Section titled “GET /workflows/{id}”Returns a single workflow by ID. The primary interface for polling workflow completion.
GET /workflows/WTjV1t4GResponse shape is identical to items in GET /workflows.
Completion detection:
Poll until state is completed, then fetch results via
POST /pubkeys-set/txns,
POST /pubkeys-set/utxos,
or GET /workflows/{id}/results/core.
while (true) { const workflow = await get(`/workflows/${id}`) if (workflow.state === 'completed') break if (workflow.state === 'cancelled') throw new Error('workflow cancelled') if (workflow.state === 'failed') throw new Error('workflow failed') await sleep(pollInterval)}404 response:
A 404 means the workflow ID is unknown — either it never existed, or it completed and expired from workflow history.
If you submitted the workflow successfully and received a workflow_id, treat 404 as an unexpected condition: log
an error, wait a backoff period, and resubmit the operation. Do not treat 404 as a normal polling outcome.
The appropriate backoff interval depends on your deployment topology — localhost, reverse proxy, or Tor all have meaningfully different latency characteristics.
Alternatively, subscribe to workflow.progress events to receive completion notification by push rather than polling.
DELETE /workflows/{id}
Section titled “DELETE /workflows/{id}”Cancels a pending or running workflow.
DELETE /workflows/WTjV1t4GdkResponse:
{"result": "Workflow WTjV1t4Gdk queued for deletion"}Behaviour by state:
| State | Result |
|---|---|
pending | Removed from the queue — transitions to cancelled |
running | Cancelled — in-progress step is abandoned, transitions to cancelled |
completed | No-op — workflow has already completed |
cancelled | No-op — workflow already cancelled |
failed | No-op — workflow already failed |
Cancelled workflows are added to history immediately and remain visible for up to
history_ttl seconds. A caller polling
GET /workflows/{id} after a successful DELETE will receive
state: cancelled for the duration of the retention window, not a 404.
404 response:
The workflow ID is unknown or has expired from workflow history.
POST /workflows
Section titled “POST /workflows”Submit a new workflow. Returns immediately with a workflow ID.
POST /workflowsRequest body:
{ "workflow": { "name": "Import wallet-1", "request_id": "256483", "args": { "pubkeys_set": "test/wallet-1" }, "steps": [ { "step": "pubkeys.extpubkey.import", "args": { "extpubkey": "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", "scanspec": {"from": 0, "gap_limit": 20} } }, {"step": "pubkeys.scripthash.subscribe"}, {"step": "pubkeys.scripthash.history"}, {"step": "pubkeys.transaction.fetch"}, {"step": "blockheaders.unresolved.fetch"}, {"step": "pubkeys.unspent.compute"} ] }}Response:
{ "workflow_id": "WTjV1t4GNc", "request_id": "256483"}request_id is omitted from the response if not provided in the request.
Workflow fields:
| Field | Required | Description |
|---|---|---|
name | no | Human-readable label |
request_id | no | Caller-assigned ID, echoed in response and progress events. 1–127 characters |
dry_run | no | If true, validate the workflow without submitting it |
args.pubkeys_set | yes (pubkeys workflows) | Name of the pubkeys set |
steps | yes | Array of step objects |
Dry run:
Set dry_run: true to validate a workflow submission without executing it. Useful for
debugging workflow syntax before running the workflow. On success:
{ "workflow_id": "SUCCESS", "request_id": "256483"}Validation errors are returned in the standard error envelope. See Error Responses.
Step Reference
Section titled “Step Reference”Steps are the individual units of work in a workflow. Each step is a pre-defined operation — importing addresses, fetching transactions, computing UTXOs — and a workflow is simply an ordered list of steps composed to achieve a goal. Steps execute in sequence; include only what your use case requires.
Pubkeys workflow steps:
| Step | Description |
|---|---|
pubkeys.set.create ¹ | Create pubkeys set |
pubkeys.set.delete | Clear cached records for the pubkeys set |
pubkeys.extpubkey.import | Import derived addresses into pubkeys_set ² |
pubkeys.pubkey.import | Import discrete addresses into pubkeys_set ² |
pubkeys.scripthash.subscribe | Subscribe addresses to the indexer |
pubkeys.scripthash.unsubscribe | Unsubscribe addresses from the indexer |
pubkeys.scripthash.history | Retrieve transaction history from the indexer |
pubkeys.transaction.fetch | Fetch full transaction data |
pubkeys.fee.prep | Discover previous out transactions for computing transaction fee |
transactions.unresolved.fetch | Resolve required transactions (previous out, etc) |
blockheaders.unresolved.fetch | Resolve block headers for fetched transactions |
pubkeys.unspent.compute | Compute the UTXO set |
¹ Pubkeys sets are automatically created if they don’t already exist —
pubkeys.set.create is rarely needed directly.
² Derived and discrete addresses cannot be mixed in the same pubkeys_set. A pubkeys_set is either xpub-derived or discretely composed. If you need both, use separate pubkeys_sets and query them independently. See Wallets & ScriptPubKeys.
Arguments
Section titled “Arguments”Workflows have two levels of arguments. Workflow-level args are global — shared context
available to every step. For pubkeys workflows, pubkeys_set is the primary
workflow-level arg, telling all steps which wallet they’re operating on. Step-level args
are specific to a single step and supply additional inputs that step alone requires.
Steps without their own args block rely entirely on the workflow-level args.
{ "workflow": { "args": {"pubkeys_set": "test/wallet-1"}, "steps": [ { "step": "pubkeys.extpubkey.import", "args": { "extpubkey": "zpub6rFR7y4Q2AijB...", "scanspec": {"from": 0, "gap_limit": 20} } }, {"step": "pubkeys.scripthash.subscribe"}, {"step": "pubkeys.scripthash.history"} ] }}Here pubkeys_set is the workflow-level arg — every step knows which wallet it’s
working with. extpubkey and scanspec are step-level args — only needed by
pubkeys.extpubkey.import. Steps like pubkeys.scripthash.subscribe and
pubkeys.scripthash.history have no step-level args of their own.
The following steps accept step-level args:
pubkeys.extpubkey.import
Section titled “pubkeys.extpubkey.import”Accepts an extended public key and three scanspec forms: gap_limit, count and range. See Wallets & ScriptPubKeys for details.
| Field | Required | Description |
|---|---|---|
extpubkey | yes | xpub, ypub, or zpub string |
scanspec | yes | Address derivation specification |
pubkeys.pubkey.import
Section titled “pubkeys.pubkey.import”Accepts plain addresses, raw public keys with explicit script type prefix, or both combined in a single step.
Plain addresses:
{ "step": "pubkeys.pubkey.import", "args": { "addresses": [ "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", "bc1qp7lsyxsf0p9049dgea2t0cynx6d3ama0xe3pdv" ] }}Raw public keys with explicit script type prefix:
{ "step": "pubkeys.pubkey.import", "args": { "pubkeys": [ "p2wpkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2pkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2sh-p2wpkh:02d8e638a311b13f6a097b33fead3fe9b02b1e04c9a28a87e80b3598fc7a2c4d90", "p2pk:04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f" ] }}p2sh-p2wpkh and p2shwpkh are both accepted as the prefix for wrapped SegWit keys.
Combined in a single step:
{ "step": "pubkeys.pubkey.import", "args": { "addresses": [ "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu" ], "pubkeys": [ "p2pk:04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f" ] }}Core Results
Section titled “Core Results”GET /workflows/{id}/results/core
Section titled “GET /workflows/{id}/results/core”Returns results of a core.* workflow. Each item corresponds to a step in the submitted
workflow, in order. Results are passed through verbatim from bitcoind. Refer to the
bitcoind RPC documentation for result
shapes, error codes, and method details.
GET /workflows/WTjV1t4Gdk/results/coreResponse:
[ { "step": 1, "method": "getblockcount", "args": [], "result": 292427, "error": null }, { "step": 2, "method": "getmempoolentry", "args": ["82cb1fc54a4d891d687c3898a7ebcd54da996e7f26e1fd6f2a21dc2f655194fa"], "result": null, "error": { "code": -5, "message": "Transaction not in mempool" } }]Steps with RPC errors return the bitcoind error code and message. The workflow continues executing remaining steps regardless of individual step errors.
Submitting a core workflow:
Use the core. prefix followed by the bitcoind RPC method name. Args are passed as a
JSON array in the same order as the bitcoind RPC method signature. In general, do not mix core.*
steps with pubkeys workflow steps in the same workflow.
{ "workflow": { "steps": [ {"step": "core.getblockcount"}, {"step": "core.getblockchaininfo"}, {"step": "core.estimatesmartfee", "args": [6, "ECONOMICAL"]}, {"step": "core.getbestblockhash"}, {"step": "core.getblockhash", "args": [292411]}, {"step": "core.validateaddress", "args": ["tb1q8768jg36dw0zqwkjt4wp8pp75uc2zkjhp5q9qr"]}, {"step": "core.gettxout", "args": ["<txid>", 0]}, {"step": "core.getmempoolentry", "args": ["<txid>"]}, {"step": "core.getchaintips"}, {"step": "core.getdifficulty"}, {"step": "core.getconnectioncount"}, {"step": "core.getnettotals"}, {"step": "core.uptime"} ] }}Wallet Results
Section titled “Wallet Results”Retrieve cached transaction and UTXO data for a pubkeys set workflow. Results are only available after the workflow that populated them has completed. There are two ways to know when a workflow is done:
Polling — call GET /workflows/{id} periodically until state is completed.
WebSocket — subscribe to workflow.progress and listen for the event where state
is completed. See the WebSocket reference for the
event schema.
Once complete, results are cached and can be queried as many times as needed without re-running the workflow. See Workflow Model for the full lifecycle.
See ScriptPubKeys Set for naming conventions and security considerations.
POST /pubkeys-set/txns
Section titled “POST /pubkeys-set/txns”To retrieve transactions for a pubkeys set once results are available:
POST /pubkeys-set/txnsRequest body:
{"pubkeys_set": "test/wallet-1"}Request fields:
| Field | Required | Description |
|---|---|---|
pubkeys_set | yes | Name of the pubkeys set |
limit | no | Results per page. Set to 0 to return all records. Default 100 |
offset | no | Pagination offset, default 0 |
from_block | no | Filter to transactions at or above this height |
to_block | no | Filter to transactions at or below this height |
exclude_unconfirmed | no | Exclude mempool transactions, default false |
include_fee | no | Include fee-related fields in the response, default false. See note below. |
sort_order | no | asc or desc, default desc |
Response:
{ "txns": [ { "timestamp": 1773359301, "datetime": "2026-03-12 23:48:21", "size": 222, "inputs": 1, "ins_total": 217572, "fee": 1473, "weight": 561, "outputs": 2, "outs_total": 216099, "txid": "72ab93c9e66f3cad39620f4689748704012b9f28acf896b348208e0fc0bafa73", "own_ins": [ { "txidx": 0, "prev_txid": "806cffbbd657119e03f12b5039794e180456f96b07b689bb162a5a7787ea9f2e", "prev_txidx": 1, "hdidx": "1:8", "address": "tb1qk9jg6xwkx7s8vz2mpj5w2jayqdk9lks6y00j0k", "value": 217572 } ], "own_outs": [ { "hdidx": "1:9", "address": "tb1qne62gqgn4kx3l0eh4c3yhjhjkmjhfv5j63zq9h", "txidx": 1, "value": 206980, "spent": false } ], "height": 295322 } ], "pagination": { "total": 11, "returned": 1, "limit": 1, "offset": 0 }}Confirmation status:
height | datetime | Meaning |
|---|---|---|
> 0 | block timestamp | Confirmed at this block height |
0 | absent | Unconfirmed — all inputs confirmed |
-1 | absent | Unconfirmed — at least one input unconfirmed |
Response fields:
| Field | Description |
|---|---|
txid | Transaction ID |
height | Block height. See confirmation status table above |
timestamp | Block timestamp as Unix epoch. Absent for unconfirmed transactions |
datetime | Block timestamp as UTC string. Absent for unconfirmed transactions |
size | Transaction size in bytes |
weight | Transaction weight units |
inputs | Total number of inputs |
ins_total | Total value of all inputs in satoshis. Absent for coinbase transactions. Otherwise only present when include_fee is true and all input prev-outs were resolved |
fee | Miner fee in satoshis. Absent for coinbase transactions. Otherwise only present when include_fee is true and all input prev-outs were resolved. Use with weight to compute fee rate — see note above |
outputs | Total number of outputs |
outs_total | Total value of all outputs in satoshis |
Each transaction may have many inputs and outputs, but own_ins and own_outs contain only
those belonging to addresses in the pubkeys set.
Own inputs (own_ins):
| Field | Description |
|---|---|
own_ins[].txidx | Index of this input within the transaction |
own_ins[].prev_txid | TXID of the output being spent |
own_ins[].prev_txidx | Output index within the previous transaction |
own_ins[].hdidx | HD derivation path in account:index format. Omitted for discretely imported addresses |
own_ins[].address | Address for this input |
own_ins[].value | Value in satoshis |
Own outputs (own_outs):
| Field | Description |
|---|---|
own_outs[].txidx | Index of this output within the transaction |
own_outs[].hdidx | HD derivation path in account:index format. Omitted for discretely imported addresses |
own_outs[].address | Address for this output |
own_outs[].value | Value in satoshis |
own_outs[].spent | Whether this output has been spent |
Pagination:
Results can be paginated to page through large sets. Use limit and offset to retrieve pages, or
set limit: 0 to return all records. Transactions can be filtered to a block height range using
from_block and to_block. Results can be sorted by height, ascending or descending. Unconfirmed
transactions are included by default; set exclude_unconfirmed: true to exclude them.
The pagination object describes the current page and the full result set, so you can
calculate total pages and navigate forward.
| Field | Description |
|---|---|
pagination.total | Total number of transactions matching the query |
pagination.returned | Number of transactions in this response |
pagination.limit | Page size used for this query |
pagination.offset | Offset used for this query |
All values are in satoshis.
POST /pubkeys-set/utxos
Section titled “POST /pubkeys-set/utxos”To retrieve UTXOs for a pubkeys set once results are available:
POST /pubkeys-set/utxosRequest body:
{"pubkeys_set": "test/wallet-1"}Request fields:
| Field | Required | Description |
|---|---|---|
pubkeys_set | yes | Name of the pubkeys set |
limit | no | Results per page. Set to 0 to return all records. Default 100 |
offset | no | Pagination offset, default 0 |
min_amount | no | Minimum value in satoshis |
max_amount | no | Maximum value in satoshis |
sort_by | no | height or value, default height |
sort_order | no | asc or desc, default desc |
Response:
{ "utxos": [ { "timestamp": 1771355949, "datetime": "2026-02-17 19:19:09", "value": 8505, "txid": "806cffbbd657119e03f12b5039794e180456f96b07b689bb162a5a7787ea9f2e", "txidx": 0, "height": 291998, "address": "tb1qe02jn6rsq20637hacsphxqxrdpnnmcqt9eh4q7", "hdidx": "0:8" } ], "pagination": { "total": 1, "returned": 1, "limit": 100, "offset": 0 }, "summary": { "total_count": 1, "total_amount": 8505 }}Response fields:
| Field | Description |
|---|---|
txid | Transaction ID |
height | Block height. See confirmation status table above |
timestamp | Block timestamp as Unix seconds |
datetime | Block timestamp as UTC string |
txidx | Index of this output within the transaction |
address | Address for this output |
hdidx | HD derivation path in account:index format. Omitted for discretely imported addresses |
value | UTXO value in satoshis |
Pagination:
Results can be paginated to page through large sets. Use limit and offset to retrieve pages, or
set limit: 0 to return all records. UTXOs can be filtered by value using min_amount and max_amount.
Results can be sorted by height or value, ascending or descending.
See POST /pubkeys-set/txns for full pagination details — behaviour is identical.
| Field | Description |
|---|---|
pagination.total | Total number of UTXOs matching the query |
pagination.returned | Number of UTXOs in this response |
pagination.limit | Page size used for this query |
pagination.offset | Offset used for this query |
Summary:
summary reflects the full result set, not just the current page. Use it to display
total balance and UTXO count without paginating through all results.
| Field | Description |
|---|---|
summary.total_count | Total number of UTXOs matching the query |
summary.total_amount | Total value of all matching UTXOs in satoshis |