Transactions
A transaction is an intent that flows through policy, screening, and quota checks before it can be signed and broadcast. Native transfers and token (ERC-20/QRC-20) transfers use the same endpoint — token routing is automatic based on assetId.
Create a transfer
POST/v1/transactions
{
"externalId": "payout-2026-08-001", // your idempotency handle (see below)
"sourceWalletId": "…", // required — must not be WATCH_ONLY
"destinationAddress": "0x…", // required
"assetId": "QC", // required; token assets route via ERC-20 transfer
"amount": "1000000000000000000", // required — decimal integer string, wei / raw units
"note": "August payout batch 1",
"metadata": {"batch": "2026-08"}
}
Returns 201 with the full transaction object in status SUBMITTED.
Idempotency — use both layers
externalId— domain-level: creating a second transaction with the sameexternalIdreturns the existing one instead of double-spending.X-Idempotency-Keyheader — transport-level: any POST/PUT retried with the same key within 24 hours replays the original response (markedX-Idempotent-Replayed: true). Reusing a key on a different method or path returns422 IDEMPOTENCY_CONFLICT(1601).
X-Idempotency-Key — not Idempotency-Key. The latter is silently ignored.Status lifecycle
COMPLETEDFAILEDREJECTEDCANCELLEDBLOCKED are terminal. PENDING_AML_SCREENING is also the fail-safe parking state when the screening provider itself errors — treat it as "on hold", not strictly "screening in progress".
The transaction object
{
"id": "…", "tenantId": "…", "externalId": "payout-2026-08-001",
"sourceVaultId": "…", "sourceWalletId": "…",
"destinationAddress": "0x…", "assetId": "QC",
"status": "CONFIRMING", "failureReason": "",
"chainTxHash": "0x…", "confirmations": 3,
"amount": "1000000000000000000", // decimal strings, present when set:
"fee": "…", "gasPrice": "…", "maxFeePerGas": "…", "maxPriorityFee": "…",
"gasLimit": 21000, "nonce": 17, "blockNumber": 3392847,
"signingHash": "ab34…", // hex, no 0x — only while PENDING_SIGNATURE
"note": "…", "metadata": {…},
"createdAt": "…", "updatedAt": "…", "broadcastAt": "…", "confirmedAt": "…"
}
Query, estimate, act
| Endpoint | Purpose |
|---|---|
GET/v1/transactions | List. Filters: status, vaultId, walletId, assetId, dateFrom, dateTo (RFC 3339, inclusive); sorting: sortBy=createdAt&sortOrder=asc|desc; paging: limit (default 20, max 100) + cursor |
GET/v1/transactions/{id} | Fetch one |
GET/v1/transactions/export | CSV export (same filters) |
GET/v1/transactions/estimate-fee?sourceAddress=…&destinationAddress=…&assetId=…&amount=… | Fee estimate before creating |
POST/v1/transactions/{id}/approve | Body {"approverId","reason"} — moves to PENDING_SIGNATURE |
POST/v1/transactions/{id}/reject | Body {"approverId","reason"} |
POST/v1/transactions/{id}/cancel | Cancel any pre-broadcast transaction |
Cursor pagination
List responses end with "page": {"nextCursor": "…", "hasMore": true}. Cursors are opaque and direction-bound — replaying a cursor under a different sortOrder returns 400 invalid cursor. Iterate until hasMore is false.
/v1/tokenization, which create the corresponding transactions internally and run them through the same lifecycle.
