Transactions
Transaction Lifecycle
Every transaction follows a defined state machine from creation to on-chain confirmation (or terminal failure):
| Status | Description | Next |
|---|---|---|
pending | Created, no quorum required (or quorum already satisfied at creation time). | requested, to-cancel, failed |
approval-pending | Waiting for required vault-quorum approvals. | approved, to-cancel, cancelled |
approved | Quorum reached, waiting to be sent to signing. | requested, to-cancel |
requested | Submitted to the MPC signing pipeline. | signed, to-cancel, failed |
signed | Signed by the MPC network. | submitted, failed |
submitted | Broadcast to the blockchain. | mined, replaced, failed |
mined | Confirmed on chain (terminal — happy path). | — |
to-cancel | Cancellation requested via Decline; platform is unwinding. | cancelled |
cancelled | Cancelled (terminal — see reasonCode on the event for details). | — |
failed | Failed on submission/processing (terminal). | — |
replaced | Superseded by a replacement transaction that mined (terminal). | — |
A warning event (separate from status) is emitted when processing is paused but the transaction is still alive — most commonly for insufficient balance at signing time. See Warnings and retries.
Cancellation reason codes you may see on cancelled events: insufficient_balance, singner_unavailable, not_enough_utxos, order_expired, invalid_order, transaction_expired, declined_by_client, rejected_by_approver, rejected_by_master_approver, replacement_status_invalid.
Create a Transaction
Asset IDs
The asset field accepts a Universal Asset ID, not a human-readable name. Query available assets via GET /networks/{network}/assets. Native coins use a SLIP-44–scoped identifier (e.g. c60 for native ETH on Ethereum, c0 for native BTC); token assets append a _t… segment that encodes the token contract or issuer (e.g. c60_t0xdac17f958d2ee523a2206206994597c13d831ec7 for USDT on Ethereum).
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "gMP71sR5sNUnGdKFTsNzp6",
"destination": "0x742d35Cc6634C0532925a3b8...",
"network": "ethereum-mainnet",
"asset": "c60_t0xdac17f958d2ee523a2206206994597c13d831ec7",
"amount": "1000.50",
"feePriority": "medium",
"note": "Payment to supplier"
}' \
https://api.carabaas.com/api/v1/transactions
Request fields
| Field | Required | Description |
|---|---|---|
orderId | Yes | A valid UUID string. Idempotency key — see Idempotency. |
addressId | Conditional | Source address. Required for non-UTXO networks. For UTXO networks one of addressId / accountId / vaultId must be provided. |
accountId | Conditional | Source account (UTXO chains only) — collect UTXOs across all addresses in the account. |
vaultId | Conditional | Source vault (UTXO chains only) — collect UTXOs across all addresses in the vault. |
destination | Yes | Recipient on-chain address. |
network | Yes | Custody network identifier (e.g. ethereum-mainnet, bitcoin-mainnet). |
asset | Yes | Universal Asset ID for that network. |
amount | Yes | Decimal string in the asset’s human units (e.g. "1000.50"). |
feePriority | No | One of low | medium | high. Default medium. (custom is not accepted — use options.gasPrice on ETH-like networks instead.) |
note | No | Up to 256 chars. Visible only to you — saved with the transaction record. |
reference | No | Up to 128 chars. Shared field — visible to you and to the counterparty integrations. |
options | No | Network-specific knobs — see below. |
options
| Option | Applies to | Description |
|---|---|---|
gasPrice | ETH-like | Decimal string. Takes precedence over feePriority when set. |
gasLimit | ETH-like | Decimal string. |
data | ETH-like | Hex-encoded calldata (0x…) for smart-contract interaction. |
memo | XRP, ATOM, EOS, HBAR, LUNA/LUNC, XDB, XEM, XLM, ALGO | Destination tag / memo. Wire format depends on the chain. |
changeAddressId | UTXO | Address that should receive the change output. Must belong to the source vault. |
feePayerAddressId | Solana | Address paying the network fee. Must belong to the source vault. |
Source selection
| Network family | Required source fields |
|---|---|
| Account-based (ETH-like, Tezos, Solana, Tron, Cosmos, Ripple, Stellar, …) | addressId is required. |
| UTXO (Bitcoin, Litecoin, Dogecoin, Bitcoin Cash, Dash) | One of addressId / accountId / vaultId. When sending from accountId / vaultId, also pass options.changeAddressId. |
For UTXO specifics — change handling, address types — see Bitcoin & UTXO Chains.
If you pass more than one source field, the platform validates they refer to the same vault/account; mismatches return 400.
Authorization
The caller must hold initiate permission on the vault that owns the source address. Without it the request is rejected with 403 before validation.
Idempotency
The orderId field is the idempotency key. Submitting the same orderId twice does not create a duplicate — the API returns the existing transaction object.
The HTTP status code distinguishes create vs replay; read it explicitly:
| HTTP | Meaning |
|---|---|
| 201 | A new transaction was created for this orderId. |
| 200 | A transaction with this orderId already exists; the same object is returned. |
Replays return the existing transaction regardless of its current status, so data.status is the canonical signal for progress. Generate a fresh UUID per logical business action; on timeouts/network errors reuse the same orderId so retries don’t spawn duplicates.
Get transaction details
# By transaction ID (default)
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}
# By blockchain hash
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/{hash}?idType=blockchainHash"
# By order ID
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/{orderId}?idType=orderId"
idType accepts id (default), blockchainHash, or orderId. The response includes lifecycle events and creator by default; approvals, decodedPayload, and quorum are returned when the caller has the relevant permissions on the vault.
Decoded payload
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/decoded
Returns the transaction with its decoded payload — useful for verifying the exact operation that will be signed (calldata decoding, recipients, amounts) before approving.
Lifecycle events
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/events
Returns the full event timeline (pending, approval-pending, approved, signed, submitted, mined, warning, failed, cancelled, replaced, …) with timestamps and data payloads. Use this to reconstruct what happened to a transaction post-hoc; for live updates rely on Webhooks.
List transactions
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions?organizationId=djk2wDuMhsx9KR2r7JgBQW"
organizationId is required. Vault scoping is automatic — without manageVaults on the organization (or Vault:read global), results are restricted to vaults the caller has read on.
Filters
All filters use bracket notation and accept arrays (repeat the parameter, or use comma-separated values per your HTTP client).
| Filter | Notes |
|---|---|
filter[id] | Internal transaction IDs. |
filter[orderId] | Your orderIds. |
filter[hash] | On-chain transaction hashes. |
filter[vaultId] | Vault IDs (intersected with the caller’s accessible vaults). |
filter[accountId] | Account IDs. |
filter[network] | Custody network codes. |
filter[asset] | Universal Asset IDs. |
filter[sourceAddressId] | Source address IDs. |
filter[status] | One or more of the lifecycle statuses (see Transaction Lifecycle). |
filter[creatorId] | Client IDs of transaction creators. |
relations[] | Hydrate optional relations: vault, account, address, events, approvals, creator. |
search | Free-text search over address name / network address / hdpath (min 3 chars). |
page, pageSize | Defaults page=0, pageSize=100. Max pageSize=1000. |
# Status + network, hydrate vault and approvals
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions\
?organizationId=djk2wDuMhsx9KR2r7JgBQW\
&filter[status]=mined\
&filter[network]=ethereum-mainnet\
&relations[]=vault&relations[]=approvals"
The response is paginated:
{
"data": [ /* transactions */ ],
"pagination": { "total": 1234, "page": 0, "pageSize": 100, "totalPages": 13 },
"empty": false,
"hasMore": true
}
Blockchain-sourced transactions
List on-chain transactions involving your vault addresses (both outgoing and incoming/external):
curl -H "Authorization: Bearer $TOKEN" \
"https://api.carabaas.com/api/v1/transactions/blockchain\
?vaultId=kR7mNpX2wQvL9sYhBjD4eT&network=ethereum-mainnet"
One of vaultId / accountId / sourceAddressId is required; network is optional. Pagination uses page / pageSize and a hasMore cursor (no total).
Simulate a transaction
Preview fee/balance impact for a candidate transaction without persisting anything:
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "gMP71sR5sNUnGdKFTsNzp6",
"destination": "0x742d35Cc...",
"network": "ethereum-mainnet",
"asset": "c60",
"amount": "1.0"
}' \
https://api.carabaas.com/api/v1/transactions/simulate
The body is a regular CreateTxDto. The endpoint runs the simulation for all three fee priorities (low, medium, high) and returns an array of results, each with the projected balanceChanges and chain-specific metadata (e.g. gasPrice, gasLimit on ETH-like).
Approve a transaction
When a transaction sits in approval-pending, approvers in the vault’s quorum sign it via:
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"keyId": "the-approver-key-id",
"approval": "<signature-over-approvalString>"
}' \
https://api.carabaas.com/api/v1/transactions/{txId}/approve
Constraints:
- Caller must have
approvepermission on the vault. - Transaction must currently be in
approval-pending; otherwise 400Transaction is not in approval-pending status. - Transaction must have a quorum attached; otherwise 400
Transaction has no quorum associated.
The signed string is over the transaction’s approvalString (deterministic, returned in the transaction object). Once the quorum threshold is met the transaction transitions to approved and proceeds automatically. See Approval Workflow for the end-to-end signing protocol.
List approvals
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/approvals
Returns approvals collected so far, each with the approving key and client. Caller needs read on the vault (or manageVaults on the org); approver identity (hideApprovers) is hidden unless the caller also has approve on the vault or Vault:read globally.
Master approval payload
curl -H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/master-approval-payload
Returns { approvalString, approvals, encodedTransaction, payloads } — the bundle a master approver needs to issue the final signature. Caller must have approve on the vault, and the transaction must already have its payload constructed (otherwise 400 Transaction has no payload constructed yet).
Decline a transaction
curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
https://api.carabaas.com/api/v1/transactions/{txId}/decline
Requests cancellation of a transaction that has not yet reached a terminal state. The transaction transitions to to-cancel while the platform unwinds processing, then to cancelled (reasonCode: declined_by_client).
Caller must have decline permission on the vault. Cancellation is best-effort: once a transaction is submitted to the network, it can no longer be safely declined — use Replace for ETH-like networks or wait for the network outcome.
Replace a transaction
Replace a still-unmined transaction with a higher-fee version (e.g. RBF / fee bump):
curl -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{ "feePriority": "high" }' \
https://api.carabaas.com/api/v1/transactions/{txId}/replace
Body fields (all optional):
| Field | Notes |
|---|---|
feePriority | low | medium | high. Default for replacement is high. |
gasPrice | Decimal string. Overrides feePriority when set. |
gasLimit | Decimal string. Falls back to the parent transaction’s value if omitted. |
Constraints (return 400 otherwise):
- Only Ethereum-like networks support replacement.
- The original transaction must be in
submittedstatus. - A replacement transaction itself cannot be replaced — bump the parent instead.
Caller must have initiate on the vault. The replacement carries a new transaction id but reuses the parent’s orderId; on success the parent eventually transitions to replaced (terminal) once the replacement mines.
Warnings and retries
When the platform pauses processing for a transient reason it emits a warning event (in addition to the lifecycle status, which stays unchanged) and retries automatically. Reason codes you’ll see on the warning payload:
| Reason code | What it means |
|---|---|
insufficient_balance | Source address can’t cover amount + fee at signing time. Top up the address; the platform will pick the transaction back up. |
singner_unavailable | An MPC signer is temporarily offline. Resolves automatically when signers return. |
If the underlying condition resolves, processing continues from where it stopped — no new transaction is created, the same id/orderId proceeds. If it doesn’t resolve within the platform’s retry window, the transaction is cancelled with a matching reasonCode. Subscribe to webhooks (warning, cancelled) to surface this to your users.
Special transaction types
The base POST /api/v1/transactions flow handles standard transfers. The following endpoints create specialised custody transactions that share the same lifecycle, idempotency, and approval semantics:
Trustline (Stellar / Ripple)
POST /api/v1/transactions/trustline — open an asset trustline so the address can hold a non-native token. See Stellar & Ripple — Trustlines.
Reveal (Tezos)
POST /api/v1/transactions/reveal — one-time on-chain public-key reveal required before any outgoing Tezos operation. See Tezos Reveal.
ERC-20 / TRC-20 transfer-from
POST /api/v1/transactions/transfer-from — pull tokens previously approved to a spender address.
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orderId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"addressId": "8uPDmg3KsbUfsB8dx",
"spenderAddressId": "mnfUjfKLCxFbLZQmB",
"destination": "0xRecipient...",
"network": "ethereum-mainnet",
"asset": "c60_t0x779877A7B0D9E8603169DdbD7836e478b4624789",
"amount": "150",
"feePriority": "medium"
}' \
https://api.carabaas.com/api/v1/transactions/transfer-from
Both addressId (the token holder / from) and spenderAddressId (the address paying gas in the native coin) must belong to the same vault.
Setup-allowance flow
POST /api/v1/transactions/flows/setup-allowance — multi-step orchestrated flow that configures an ERC-20/TRC-20 allowance from ownerAddressId for spenderAddressId.
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ownerAddressId": "8uPDmg3KsbUfsB8dx",
"spenderAddressId": "mnfUjfKLCxFbLZQmB",
"network": "ethereum-mainnet",
"asset": "c60_t0x779877A7B0D9E8603169DdbD7836e478b4624789",
"feePriority": "medium"
}' \
https://api.carabaas.com/api/v1/transactions/flows/setup-allowance
orderId (UUID) is optional — if provided it acts as the flow-level idempotency key. The response is a Flow object (not a Transaction); the underlying erc20_approve / trc20_approve transactions are created inside the flow.
Manage flows via:
GET /api/v1/transactions/flows/{id}— fetch byid(default) ororderId(?idType=orderId).PATCH /api/v1/transactions/flows/{id}/decline— request flow cancellation.