GET /transactions, GET /transfers, GET /wallets, GET /runs, GET /approvals and
GET /webhook_deliveries are paged as described below. GET /webhook_endpoints, GET /vaults,
GET /workflows, GET /networks, GET /assets and GET /prices are short and answer in one
response.
One network per list
A list of data that lives on a network names it:?network=mainnet. Without it the answer is
422 network_required; there is no “all networks”. A merchant on two networks reads each one
separately. GET /networks takes no network: it is where the values come from. Neither do
GET /webhook_endpoints and GET /webhook_deliveries: a webhook endpoint hears every network.
Pages
A page islimit rows, newest first, and the cursor of the next page:
Pass
next_cursor back as cursor, with the same filters, until it is null. A cursor is opaque:
do not build or edit one. A cursor this API did not issue answers 422 invalid_cursor; start again
from the first page.
The cursor is a position, not a snapshot. A walk to the end returns every row that was there when
it started, each once. A row that arrives during the walk is not certain to show up in it:
created_at is not the order in which rows become readable (see
Catch up after missed webhooks), so a late row can land among
the pages you have already read. To see what arrived since, read again from a margin before the
newest row you have.
Filters
Each list documents its filters in the API reference. They combine: every filter you send must hold. A filter is one string:status, direction and type take several values separated by
commas in it (status=confirmed,failed), and so does kind of GET /approvals. A filter sent as a list or an object
(status[]=confirmed, {"type": ["run.failed"]}) answers 422; it is never ignored.
from and to bound the time Citadel recorded the row (created_at): from inclusive, to
exclusive, both ISO 8601 date-times such as 2026-10-01T00:00:00Z.
A filter that names a wallet takes your reference on the network, or the wallet’s id, like
GET /wallets/{reference}. An unknown wallet answers 404; a wallet id from another network,
422 network_mismatch. An unknown filter value (a status that does not exist) answers 422.
Catch up after missed webhooks
Webhooks tell you when to look; the lists are the record. After downtime, or when an endpoint was disabled, page each list from a point before where you stopped:created_at is not the order in which rows become readable. Citadel records transfers in
batches, the chains of one network in parallel, and stamps a row’s created_at when its batch
starts, not when the batch completes. A deposit on one chain can therefore become readable after
a deposit on another chain with a later created_at. So never start a catch-up at the exact
created_at you stopped at: start 15 minutes before it, and deduplicate.
- Deposits:
GET /transfers?direction=in,from15 minutes before thecreated_atof the last deposit you credited. Each row is the body ofblockchain_event.confirmed: credit it the same way, deduplicated ontx_hash. The rows inside the margin are mostly deposits you already credited; the one you have not is the reason for the margin. To catch up on one token, addasset=with itscontractfromGET /assets. - Payouts:
GET /transactions,from15 minutes before you sent the oldest payout still in flight on your side. A transaction keeps its place in the list (the time Citadel accepted it) when its status changes, so afromlater than that would miss it; the margin also covers a difference between your clock and Citadel’s. Each row is the bodyGET /transactions/{id}reads: a webhook you missed is a row whosestatusyou have not seen yet. - Workflow runs:
GET /runs,from15 minutes before you sent the oldest event whose runs are still open on your side. A run keeps its place in the list (the time it started) when its status changes. To find the runs of onePOST /eventscall, filter on itsidempotency_key.
from.
Keys limited to vaults
A key limited to vaults lists only what is theirs: the wallets in them, the transactions from those wallets and their approvals, the transfers that touch them, and the vaults themselves. In a transfer that also touches a wallet outside them, that leg readswallet: null. GET /runs and
GET /workflows refuse such a key with 403 vault_limited, and GET /approvals lists no runs for
it: a workflow chooses its own wallets. GET /webhook_deliveries refuses it too: a webhook
endpoint serves every vault.