Skip to main content
Transactions are the core operation of the Custody API. Every transfer moves through a deterministic state machine from creation to on-chain confirmation.

Creating a transaction

Submit a transfer with source, destination, amount, and asset:
curl -X POST "$BASE_URL/transactions" \
  -H "Authorization: Bearer $CUSTODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "asset_id": "QC_NATIVE",
    "source": {
      "type": "VAULT_ACCOUNT",
      "id": "va_def456"
    },
    "destination": {
      "type": "ONE_TIME_ADDRESS",
      "one_time_address": {
        "address": "0x5678...efgh"
      }
    },
    "amount": "10.0",
    "note": "Payment"
  }'

Source and destination types

TypeDescription
VAULT_ACCOUNTA vault account managed by the Custody API
ONE_TIME_ADDRESSAn external address used for a single transfer

State machine

States

StateDescriptionDuration
CREATEDJust created, policy evaluation imminentInstant
PENDING_APPROVALAwaiting manual approval per policyUntil approved/rejected
APPROVEDApproved, transitioning to signingInstant
SIGNING_REQUESTEDAwaiting external Dilithium3 signatureUntil you sign
SIGNEDSignature received and verifiedSeconds
BROADCASTINGSubmitted to Quantum Chain nodesSeconds
CONFIRMINGIn a block, waiting for confirmation depth~15–60 seconds
COMPLETEDConfirmed with sufficient block depthFinal
FAILEDReverted or broadcast failureFinal
REJECTEDDenied by policy or approverFinal
CANCELLEDCancelled before broadcastFinal
SIGN_FAILEDInvalid signature submittedFinal

Signing flow

1

Get the signing payload

curl "$BASE_URL/transactions/{id}/signing_payload" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"
Returns a hex-encoded byte array representing the unsigned transaction digest.
2

Sign with Dilithium3

Use your external key management system to produce a Dilithium3 signature over the digest bytes.
3

Submit the signature

curl -X POST "$BASE_URL/transactions/{id}/signature" \
  -H "Authorization: Bearer $CUSTODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signature": "<base64-dilithium3-signature>",
    "signer_public_key": "<base64-public-key>"
  }'
The API verifies the signature against the registered wallet’s public key before accepting it.

Approval and rejection

If a policy requires manual approval, the transaction enters a PENDING_APPROVAL state.
# Approve
curl -X POST "$BASE_URL/transactions/{id}/approve" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"

# Reject
curl -X POST "$BASE_URL/transactions/{id}/reject" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"

Cancellation

Cancel a transaction before it has been broadcast:
curl -X POST "$BASE_URL/transactions/{id}/cancel" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"
You can only cancel transactions in PENDING_SIGNATURE or PENDING_APPROVAL states. Transactions that have been signed and broadcast cannot be cancelled.

Idempotency

Include an Idempotency-Key header to safely retry requests without creating duplicate transactions:
curl -X POST "$BASE_URL/transactions" \
  -H "Authorization: Bearer $CUSTODY_API_KEY" \
  -H "Idempotency-Key: unique-request-id-123" \
  -H "Content-Type: application/json" \
  -d '{...}'
Idempotency keys expire after 24 hours.

Listing and filtering

# List all transactions
curl "$BASE_URL/transactions" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"

# Get a specific transaction
curl "$BASE_URL/transactions/{id}" \
  -H "Authorization: Bearer $CUSTODY_API_KEY"