---
title: "Cancel a treasury transfer"
method: DELETE
path: "/v1/multisig-wallets/{walletId}/intents/{intentId}"
tags: ["Multisig Wallets"]
---

# Cancel a treasury transfer

`DELETE /v1/multisig-wallets/{walletId}/intents/{intentId}`

## Request

- Base URL: `https://api.prod.keyban.io`
- URL: `https://api.prod.keyban.io/v1/multisig-wallets/{walletId}/intents/{intentId}`
- Auth: one of:
  - API key in header `X-Api-Key`
  - `bearer` (a scheme the document does not define)

Drop a transfer still collecting signatures and purge the signatures already collected. Once the threshold is reached the relay owns it and cancelling is refused — a complete envelope stays valid on chain until the account nonce moves.

## Path parameters

- `walletId` string, uuid, required
- `intentId` string, uuid, required

## Response `200`

Transfer cancelled

- MultisigIntentDtoOutput
  - `id` string, uuid, required: Entity UUID
  - `createdAt` string, date-time, required: Creation date (ISO 8601)
  - `updatedAt` string, date-time, required: Last update date (ISO 8601)
  - `status` 'pending' | 'broadcasting' | 'unknown' | 'executed' | 'failed' | 'cancelled' | 'expired', required: Lifecycle: `pending` (collecting signatures), `broadcasting` (threshold reached, relay in progress), `executed` (mined, `txHash` set), `failed` (reverted or never broadcast), `unknown` (outcome undetermined — the transfer may have landed, and no new intent is accepted until it is reconciled), `cancelled`, `expired`.
  - `transfers` object[], required: Legs of the lot, in the exact order they were signed — the order is part of the signed preimage. The signer app displays the full list and re-derives every calldata from it.
    - `agentWalletId` string, uuid, nullable, required: Agent wallet this leg credits, as resolved at creation. Null when the leg targets a contact of the treasury instead, and on rows migrated from the single-transfer era whose agent wallet had already been deleted.
    - `targetAddress` string, required: On-chain address of that agent wallet, snapshotted so the destination stays what the signers saw even if the wallet row is later renamed or deleted.
    - `amount` string, required: Amount in USDC sub-units (6 decimals), as a positive integer string. "1500000" is 1.50 USDC.
    - `destinationLabel` string, nullable, required: Display name of this destination, resolved from the frozen `targetAddress` **at read time and never stored on the intent** — an agent wallet of the organization, else a contact of this treasury (archived ones included, so the history keeps its names). Null when the address resolves to neither. It is the frozen address above, not this label, that a signer verifies against.
    - `destinationKind` 'agent' | 'contact' | 'unknown', required: What the frozen address turned out to be: `agent` (a wallet of your own fleet — the leg is internal), `contact` (a named third party), `unknown` (neither, e.g. a wallet deleted since). Resolved from the address, so it stays true whatever path created the leg.
    - `destinationFirstUse` boolean, nullable, required: True when no transfer to this contact has ever EXECUTED — the signal the signer app puts in front of the signers, since a brand-new destination is where a compromised-channel address lands. **Null on an `agent` or `unknown` destination**: a refill toward one’s own fleet needs no such warning.
  - `totalAmount` string, required: Sum of the leg amounts in USDC sub-units, as a decimal string. Derived, never stored.
  - `fiat` object, nullable, required: Indicative rate for display only, resolved **at read time and never stored on the intent**: it is outside the signed preimage and must never enter its computation. Null when the organization has no display currency or the rate oracle is unavailable — the lot is still fully served and signable.
    - `currency` 'EUR' | 'USD' | 'JPY', required: Display currency of the organization at read time.
    - `centiPerUsdc` integer, required: Centi-units of `currency` per 1 USDC when the lot was served. A leg converts as floor(amount × centiPerUsdc / 1e6) centi-units.
  - `relayNonce` string, required: Account relay nonce the signatures are bound to, as a decimal string. One nonce covers the whole lot. If the account moves past it, the intent fails without broadcasting.
  - `relayHash` string, required: What each signer signs. Single-leg lot: `keccak256(abi.encode(chainId, account, nonce, dest, value, keccak256(data)))`. Two legs or more: `keccak256(abi.encode(chainId, account, nonce, keccak256(abi.encode(calls))))` over the ordered `(dest, value, data)` legs. The app must recompute it locally from the other fields and refuse to sign on a mismatch.
  - `approvalCount` integer, required: Signatures collected so far, out of the wallet threshold.
  - `expiresAt` string, date-time, required: End of the signing window. Past it the intent expires on its next touch and the collected signatures are purged.
  - `txHash` string, nullable: Hash of the relayed transaction. Null until executed — and can stay null on an intent reconciled as `executed` after an ambiguous broadcast, where only the consumed account nonce proves execution.
  - `error` string, nullable: Why the transfer failed, or what made its outcome undetermined. Null while pending or once executed.

## Other responses

- `404`: Multisig wallet or transfer not found
- `409`: The transfer no longer collects signatures

---

[API](https://skmtc.dev/keyban/apis/keyban-rest-apis.md) · [All operations](https://skmtc.dev/keyban/apis/keyban-rest-apis/llms.txt) · [OpenAPI document](https://skmtc.dev/keyban/apis/keyban-rest-apis/revisions/0bb79a1da961?raw)
