Integration Guide
This guide walks through a real B2B scenario end-to-end.
Scenario: ACME Corp wires $10,000 USD to Inyo. That money is split across 10 sub-wallets β one per vendor β with $1,000 each. Vendor 03 later runs a $500 preauthorization for an outstanding invoice, captures $400 of it (the invoice came in lower than expected), refunds $50 to the customer, and finally voids the remaining hold.
Every dollar remains traceable back to the original wire.
For the examples below, export these once:
export WALLET_HOST=https://{FQDN}
export TENANT=acme-corp
export API_KEY=your-tenant-api-key
Step 1 β Create the Wallets
You need 11 wallets: one "treasury" wallet that receives the wire, and 10 vendor wallets that receive the split.
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/wallets" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: acme-treasury-2026-07-14" \
-d '{
"owner_type": "company",
"owner_ref": "acme-corp",
"name": "ACME Treasury"
}'
Response:
{
"id": "f64c49bd-47a3-4cc6-b9d4-265d1fe5ba65",
"name": "ACME Treasury",
"kind": "operational",
"ownerType": "company",
"ownerRef": "acme-corp",
"createdAt": "2026-07-14T04:01:33+00:00"
}
Repeat for the 10 vendor wallets with different owner_ref and name values, and store the returned UUIDs. Below, $VENDOR_03_ID refers to vendor 03's wallet.
Naming your
Idempotency-Key: use something meaningful and unique per operation (e.g.,acme-vendor-03-2026-07-14). Reusing the same key returns the original response instead of creating a duplicate β safe to retry on network failure.
Step 2 β Deposit + Split the $10k Wire
ACME's bank confirmed the wire hit your settlement account. Record it in the ledger and split it across the 10 vendors in one atomic operation:
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/deposits" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: wire-BANKREF-20260714-001" \
-d '{
"source": {
"source_type": "wire",
"external_ref": "BANK-WIRE-98765432",
"asset_code": "USD",
"amount": "10000.00",
"received_at": "2026-07-14T14:30:00Z",
"metadata": {
"originator": "ACME Corp",
"reference": "Q3 vendor payments"
}
},
"splits": [
{"wallet_id": "<VENDOR_01_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_02_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_03_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_04_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_05_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_06_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_07_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_08_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_09_ID>", "amount": "1000.00"},
{"wallet_id": "<VENDOR_10_ID>", "amount": "1000.00"}
]
}'
Response (abbreviated β the full response has all 11 entries):
{
"id": "419b03c7-7679-4bce-b3d9-d90f75418602",
"kind": "deposit",
"externalRef": "wire-BANKREF-20260714-001",
"metadata": {
"source_uuid": "268eda6f-ed57-4e8c-9a07-1b39f15ec817"
},
"entries": [
{ "assetCode": "USD", "direction": "debit", "amount": "10000.00000000", "lineageRef": null },
{ "assetCode": "USD", "direction": "credit", "amount": "1000.00000000", "lineageRef": [7] }
],
"createdAt": "2026-07-14T14:30:12+00:00"
}
What just happened:
- A source row was created recording the wire.
- A journal of kind
depositwas written with 11 entries: one debit of $10,000 against the external-source account ("money coming from outside the ledger") and 10 credits of $1,000 against the vendor wallets. - Every credit entry carries a
lineageRefpointing at the source. - Sum of debits equals sum of credits β the double-entry invariant holds.
The source.amount must equal the sum of splits β otherwise the API returns a LEDGER_IMBALANCE error.
To keep a portion of the wire in a system suspense account (settlement fees, ops float), pass
gateway_suspense_amountat the top level. Thensum(splits) + gateway_suspense_amountmust equalsource.amount.
Accepted source_type values: wire, ach_debit, card, stablecoin_deposit.
Step 3 β Check Balances
curl -sS "$WALLET_HOST/organizations/$TENANT/wallets/$VENDOR_03_ID/balance" \ -H "X-API-KEY: $API_KEY"
[
{
"assetCode": "USD",
"posted": "1000.00000000",
"held": "0.00000000",
"available": "1000.00000000",
"updatedAt": "2026-07-14T14:30:12+00:00"
}
]
Vendor 03 has $1,000 available. If a wallet holds multiple assets, filter with ?asset=USD β this returns a single-element array (or empty if no balance in that asset).
Step 4 β Preauthorize $500
Vendor 03 has an incoming invoice. Reserve $500 before you know the exact amount so the money can't be spent elsewhere:
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/holds" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: preauth-invoice-INV-4521" \
-d "{
\"wallet_id\": \"$VENDOR_03_ID\",
\"asset_code\": \"USD\",
\"amount\": \"500.00\",
\"expires_at\": \"2026-07-21T14:30:00Z\"
}"
Response:
{
"id": "19f60a03-7a09-4184-bf66-23ce30115377",
"assetCode": "USD",
"amount": "500.00",
"status": "open",
"expiresAt": "2026-07-21T14:30:00Z",
"resolvedAt": null,
"createdAt": "2026-07-14T15:00:00+00:00",
"captures": []
}
Save the hold id β you'll capture/void against it. The balance now shows:
[
{ "assetCode": "USD", "posted": "500.00000000", "held": "500.00000000", "available": "0.00000000" }
]
posteddropped from $1,000 to $500 β the preauth debited the wallet and credited a per-wallet hold sub-account.heldshows the $500 reservation.availableis $0 β every remaining dollar is reserved.
expires_atis advisory. The hold records the expiration, but nothing auto-voids expired holds. Track hold IDs and expirations on your side and void explicitly when they lapse.
Step 5 β Capture $400 of the $500 Hold
The invoice came in at $400. Capture that amount into a destination account β typically a payables/settlement wallet you created beforehand:
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/holds/$HOLD_ID/capture" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: capture-INV-4521" \
-d "{
\"amount\": \"400.00\",
\"destination_account_id\": \"$PAYABLES_WALLET_ID\"
}"
Response:
{
"id": "b092a884-8cc4-479d-80b5-995e668a8cdb",
"kind": "capture",
"metadata": {
"hold_uuid": "19f60a03-7a09-4184-bf66-23ce30115377",
"captured_amount": "400.00"
},
"entries": [
{ "assetCode": "USD", "direction": "debit", "amount": "400.00000000", "lineageRef": [7] },
{ "assetCode": "USD", "direction": "credit", "amount": "400.00000000", "lineageRef": [7] }
]
}
Save the journal
id(b092a884-...) β this is the capture journal UUID, required to refund.
Balance after capture: posted 500 / held 100 / available 400. The $400 was released from the hold and moved to the destination; the $100 that was reserved above the invoice amount stays held.
Partial captures: you can capture multiple times against the same hold until the cumulative amount equals the hold amount. Status transitions open β partially_captured β captured. Once fully captured, no further captures or voids are possible β only refunds.
Step 6 β Refund $50 of the $400 Capture
The customer disputed part of the invoice. Refund uses the capture journal UUID from step 5, not the hold ID:
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/captures/$CAPTURE_JOURNAL_UUID/refund" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: refund-INV-4521-partial" \
-d '{ "amount": "50.00" }'
The refund debits the destination (payables wallet) and credits back to the originating wallet (Vendor 03) β the ledger looks up the original hold to find the source wallet. Lineage is preserved through the reversal chain.
Balance: posted 550 / held 100 / available 450.
You can refund partially multiple times until the cumulative refunded amount equals the captured amount. A fully-refunded capture rejects further refunds.
Step 7 β Void the Remaining $100 Hold
The invoice is settled ($400 captured, $50 refunded). Release the remaining $100:
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/holds/$HOLD_ID/void" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: void-INV-4521-remainder" \
-d '{}'
Balance: posted 650 / held 0 / available 650.
Vendor 03's final position: $1,000 in β $400 captured + $50 refunded = $650. Every dollar traceable back to the original wire.
Step 8 β Transfer Between Wallets
Move $200 from Vendor 03 to Vendor 07 (same currency):
curl -sS -X POST "$WALLET_HOST/organizations/$TENANT/transfers" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: transfer-v03-to-v07-20260714" \
-d "{
\"from_wallet_id\": \"$VENDOR_03_ID\",
\"to_wallet_id\": \"$VENDOR_07_ID\",
\"asset_code\": \"USD\",
\"amount\": \"200.00\"
}"
The response is a kind: transfer journal with two entries. Transfers are same-currency only β for cross-asset conversions, use POST /fx-conversions with explicit rate and rate_source fields (both are echoed back in the journal's metadata for audit).
Reading the Ledger
List every debit and credit on a wallet, most recent first (paginated, 50 per page; filter by asset, journal_kind, and date range):
curl -sS "$WALLET_HOST/organizations/$TENANT/wallets/$VENDOR_03_ID/entries?asset=USD" \ -H "X-API-KEY: $API_KEY"
Each entry includes lineageRef β the source IDs that funded that specific movement β letting you trace any debit backwards to prove where every dollar came from.
Fetch a hold with its capture history:
curl -sS "$WALLET_HOST/organizations/$TENANT/holds/$HOLD_ID" \ -H "X-API-KEY: $API_KEY"
There is no list endpoint for holds β store hold IDs (and their expirations) in your own system as you create them.
See the API Reference for the full endpoint table, error codes, and idempotency details.
