Quick Start¶
End-to-end walkthrough of the core API: create a vault, create a wallet, check the balance, estimate a fee, submit a withdrawal, and track it.
Prerequisites
You need an API Key ID and Secret. Your tenant administrator creates them in the admin dashboard under Settings → API Keys. See Authentication for the HMAC-SHA256 signing algorithm.
Authentication¶
Every request requires three HMAC signature headers. See Authentication for the full signing algorithm and code examples.
All examples below use $HEADERS as shorthand for:
Step 1: Create a Vault¶
A vault represents your end-customer whose assets will be held in custody.
curl -X POST {{baseUrl}}/vaults \
$HEADERS \
-H "Content-Type: application/json" \
-d '{
"externalId": "cust_12345",
"name": "Alice Johnson"
}'
Response (201):
{
"id": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"externalId": "cust_12345",
"name": "Alice Johnson",
"metadata": {},
"status": "Active",
"createdAt": "2026-02-22T10:00:00Z"
}
Note the vault id -- you will use it when creating wallets.
Step 2: Create a Wallet¶
Create a wallet for a vault. You need the vault ID from Step 1 and an asset ID (available from the dashboard under Assets).
curl -X POST {{baseUrl}}/vaults/d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890/wallets \
$HEADERS \
-H "Content-Type: application/json" \
-d '{
"assetId": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"label": "Primary ETH Wallet"
}'
Response (201):
{
"id": "f4a5b6c7-d8e9-0123-fabc-456789abcdef",
"vaultId": "d1e2f3a4-b5c6-7890-d1e2-f3a4b5c67890",
"assetId": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"assetName": "Ethereum",
"assetSymbol": "ETH",
"network": "Ethereum",
"label": "Primary ETH Wallet",
"depositAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18",
"depositTag": null,
"status": "Active",
"createdAt": "2026-02-22T10:30:00Z"
}
The depositAddress is the on-chain address where this wallet can receive funds. Share this with your end-customers for deposits.
Step 3: Check Wallet Balance¶
Response (200):
| Field | Description |
|---|---|
available |
Balance available for withdrawal or transfer. |
pending |
Incoming deposits not yet confirmed on-chain. |
locked |
Funds reserved by in-flight transactions. |
total |
Sum of available + pending + locked. |
Step 4: Estimate Fees¶
Before submitting a withdrawal, estimate the network fee.
curl -X POST {{baseUrl}}/transactions/estimate-fee \
$HEADERS \
-H "Content-Type: application/json" \
-d '{
"sourceWalletId": "f4a5b6c7-d8e9-0123-fabc-456789abcdef",
"destinationAddress": "0xABC1234567890DEF1234567890abcDeF12345678",
"amount": "0.5000"
}'
Response (200):
{
"low": {
"networkFee": "0.000049051962603",
"gasPrice": "1.075798425",
"gasLimit": "21000",
"feePerByte": null
},
"medium": {
"networkFee": "0.000049051962603",
"gasPrice": "1.169768470",
"gasLimit": "21000",
"feePerByte": null
},
"high": {
"networkFee": "0.000056458147116",
"gasPrice": "2.369911802",
"gasLimit": "21000",
"feePerByte": null
}
}
Three fee tiers — low, medium, high — let you offer the user a
priority/cost trade-off. Each tier carries networkFee (the estimated
total in the native asset, the only field you need for display) plus
the chain-specific inputs that produced it:
| Field | When it appears |
|---|---|
gasPrice / gasLimit |
EVM chains (ETH, USDT-ERC20, etc.) — networkFee = gasPrice × gasLimit, denominated in the native asset |
feePerByte |
UTXO chains (BTC) — fee is per-byte of the signed transaction |
Pick a tier client-side (most integrations default to medium) and
pass feeLevel: "Low" \| "Medium" \| "High" on the withdrawal request
below. Omit it and the withdrawal defaults to medium.
Step 5: Create a Withdrawal¶
Submit a withdrawal to an external address.
Idempotency-Key is required
Every fund-moving request (/transactions/withdraw,
/transactions/transfer) requires an Idempotency-Key header.
Generate a unique value per logical operation (UUID, ULID, or a stable
business identifier — up to 64 chars, [A-Za-z0-9_-]). If your client
retries the same request after a network blip, replaying with the
same key returns the original response instead of submitting a
second withdrawal. Reusing a key with a different request body returns
400 — that's almost always a client bug.
curl -X POST {{baseUrl}}/transactions/withdraw \
$HEADERS \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"sourceWalletId": "f4a5b6c7-d8e9-0123-fabc-456789abcdef",
"destinationAddress": "0xABC1234567890DEF1234567890abcDeF12345678",
"amount": "0.5000",
"note": "Monthly treasury sweep"
}'
Response (201):
{
"id": "b6c7d8e9-f0a1-2345-bcde-6789abcdef01",
"type": "Withdrawal",
"status": "Submitted",
"sourceWalletId": "f4a5b6c7-d8e9-0123-fabc-456789abcdef",
"assetSymbol": "ETH",
"network": "Ethereum",
"amount": "0.5000",
"note": "Monthly treasury sweep",
"createdAt": "2026-02-22T11:15:00Z"
}
Step 6: Track the Transaction¶
Poll the transaction endpoint or set up webhooks for real-time notifications.
Transaction Status Reference¶
| Status | Description |
|---|---|
Submitted |
Request received by the platform. |
PendingSignature |
Awaiting signing by the underlying custody backend. |
Broadcasting |
Signed and submitted to the blockchain network. |
Confirming |
On-chain, waiting for confirmations. |
Completed |
Fully confirmed and settled. |
Failed |
Transaction failed (see failureReason). |
Cancelled |
Transaction was cancelled before execution. |
Bonus: Internal Transfers¶
Transfer funds between two wallets in the same tenant (same asset). The
same Idempotency-Key rule applies — see the warning above.
curl -X POST {{baseUrl}}/transactions/transfer \
$HEADERS \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"sourceWalletId": "f4a5b6c7-d8e9-0123-fabc-456789abcdef",
"destinationWalletId": "11223344-5566-7788-99aa-bbccddeeff00",
"amount": "0.2500",
"note": "Fund user wallet"
}'
Internal transfers settle instantly with no network fees.
What's Next¶
- Set up webhooks -- Receive real-time notifications instead of polling. See the Webhooks guide.
- Address book -- Save frequently used addresses for convenience. See Address Book.