Treasury Controls
Money-out controls for treasury and business accounts. Secret key required.
Background: a claim always pays the account's own wallet — that is guaranteed on-chain and needs no controls. Moving funds to any other address requires the flow on this page:
- The destination must be on the account's allowlist — new entries activate after a 24-hour timelock.
- If your organization has dual approval enabled, every withdrawal must be approved by a different API key than the one that created it.
- The approved withdrawal yields an unsigned transfer transaction — your multisig signs it like any other transaction.
Withdrawal lifecycle: pending_approval → built → submitted → confirmed, with expired when the 24-hour approval window lapses. A successful approval builds the transfer immediately, so the status moves straight to built; on-chain progress is tracked through the withdrawal's intentId. Without dual approval, withdrawals start at built.
POST /v1/treasury/allowlist
Add a destination address. Supports Idempotency-Key.
Body: {accountId, chain, address, label?} — label ≤128 chars.
{ "id": "al_01h…", "status": "pending_activation", "activatesAt": "2026-06-16T08:00:00Z" }
Activation is lazy: an entry flips from pending_activation to active the first time it is read or used after the timelock elapses, and the treasury.allowlist.activated webhook fires then.
Errors: conflict (address already listed for this account) · invalid_request (account is not treasury/business).
GET /v1/treasury/allowlist?accountId=…
List entries as {data: […]}.
DELETE /v1/treasury/allowlist/:id
Removal is immediate (removing an address is risk-reducing; only additions are timelocked).
POST /v1/treasury/withdrawals
Create a withdrawal. Supports Idempotency-Key.
Body: {accountId, allowlistId, amount} — amount is a positive decimal string; the allowlist entry must be active.
- With dual approval: response
{id: "twd_…", status: "pending_approval", amount}and atreasury.withdrawal.pending_approvalwebhook fires. - Without: response includes
status: "built", anintentId, and the unsignedtransactionimmediately.
Errors: invalid_request (entry still inside the 24 h timelock) · not_found (entry unknown, removed, or belongs to a different account).
POST /v1/treasury/withdrawals/:id/approve
Must be called with a different secret key than the creator's — same key returns permission_denied.
{
"id": "twd_01h…", "status": "built", "intentId": "txi_…",
"transaction": { "format": "sui:tx-bytes-base64", "data": "…" }
}
Sign and submit like any transaction; track on-chain progress via the intentId. A treasury.withdrawal.approved webhook fires. Approvals expire after 24 h (status: "expired", invalid_request on a late approve).
GET /v1/treasury/withdrawals
Query: accountId=, status=, limit= (default 20, max 100). Items include approvals[] {keyId, keyPrefix, at} for audit.
Webhooks
treasury.allowlist.activated · treasury.withdrawal.pending_approval · treasury.withdrawal.approved · treasury.withdrawal.confirmed.