Guia de Integração
Este guia percorre um cenário B2B real de ponta a ponta.
Cenário: A ACME Corp envia um wire de $10.000 USD para a Inyo. Esse dinheiro é dividido entre 10 sub-carteiras — uma por fornecedor — com $1.000 cada. O Fornecedor 03 depois executa uma pré-autorização de $500 para uma fatura pendente, captura $400 dela (a fatura veio menor que o esperado), reembolsa $50 ao cliente e, por fim, cancela (void) a retenção restante.
Cada dólar permanece rastreável até o wire original.
Para os exemplos abaixo, exporte estas variáveis uma vez:
export WALLET_HOST=https://{FQDN}
export TENANT=acme-corp
export API_KEY=your-tenant-api-key
Passo 1 — Crie as Carteiras
Você precisa de 11 carteiras: uma carteira "tesouraria" que recebe o wire e 10 carteiras de fornecedores que recebem a divisão.
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"
}'
Resposta:
{
"id": "f64c49bd-47a3-4cc6-b9d4-265d1fe5ba65",
"name": "ACME Treasury",
"kind": "operational",
"ownerType": "company",
"ownerRef": "acme-corp",
"createdAt": "2026-07-14T04:01:33+00:00"
}
Repita para as 10 carteiras de fornecedores com valores diferentes de owner_ref e name, e armazene os UUIDs retornados. Abaixo, $VENDOR_03_ID refere-se à carteira do fornecedor 03.
Nomeando sua
Idempotency-Key: use algo significativo e único por operação (ex.:acme-vendor-03-2026-07-14). Reutilizar a mesma chave retorna a resposta original em vez de criar uma duplicata — seguro para retentar em falha de rede.
Passo 2 — Deposite + Divida o Wire de $10 mil
O banco da ACME confirmou que o wire chegou à sua conta de liquidação. Registre-o no ledger e divida-o entre os 10 fornecedores em uma única operação atômica:
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"}
]
}'
Resposta (abreviada — a resposta completa tem todas as 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"
}
O que acabou de acontecer:
- Uma linha de origem (source) foi criada registrando o wire.
- Um journal do tipo
depositfoi gravado com 11 entries: um débito de $10.000 contra a conta de origem externa ("dinheiro vindo de fora do ledger") e 10 créditos de $1.000 nas carteiras dos fornecedores. - Toda entry de crédito carrega um
lineageRefapontando para a origem. - A soma dos débitos é igual à soma dos créditos — a invariante das partidas dobradas se mantém.
O source.amount deve ser igual à soma das divisões — caso contrário, a API retorna um erro LEDGER_IMBALANCE.
Para manter uma parte do wire em uma conta suspense do sistema (tarifas de liquidação, float operacional), passe
gateway_suspense_amountno nível superior. Nesse caso,sum(splits) + gateway_suspense_amountdeve ser igual asource.amount.
Valores aceitos de source_type: wire, ach_debit, card, stablecoin_deposit.
Passo 3 — Verifique os Saldos
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"
}
]
O Fornecedor 03 tem $1.000 disponíveis. Se uma carteira mantiver múltiplos ativos, filtre com ?asset=USD — isso retorna um array de um único elemento (ou vazio, se não houver saldo naquele ativo).
Passo 4 — Pré-autorize $500
O Fornecedor 03 tem uma fatura a caminho. Reserve $500 antes de saber o valor exato, para que o dinheiro não possa ser gasto em outro lugar:
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\"
}"
Resposta:
{
"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": []
}
Guarde o id da retenção (hold) — você fará capturas/cancelamentos contra ele. O saldo agora mostra:
[
{ "assetCode": "USD", "posted": "500.00000000", "held": "500.00000000", "available": "0.00000000" }
]
postedcaiu de $1.000 para $500 — a pré-autorização debitou a carteira e creditou uma sub-conta de retenção por carteira.heldmostra a reserva de $500.availableé $0 — cada dólar restante está reservado.
expires_até apenas informativo. A retenção registra a expiração, mas nada cancela automaticamente retenções expiradas. Acompanhe os IDs de retenção e as expirações do seu lado e cancele (void) explicitamente quando vencerem.
Passo 5 — Capture $400 da Retenção de $500
A fatura veio em $400. Capture esse valor para uma conta de destino — tipicamente uma carteira de contas a pagar/liquidação que você criou previamente:
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\"
}"
Resposta:
{
"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] }
]
}
Guarde o
iddo journal (b092a884-...) — este é o UUID do journal de captura, exigido para reembolsar.
Saldo após a captura: posted 500 / held 100 / available 400. Os $400 foram liberados da retenção e movidos para o destino; os $100 que estavam reservados acima do valor da fatura permanecem retidos.
Capturas parciais: você pode capturar múltiplas vezes contra a mesma retenção até que o valor cumulativo seja igual ao valor da retenção. O status transiciona open → partially_captured → captured. Uma vez totalmente captured, não são mais possíveis capturas ou cancelamentos — apenas reembolsos.
Passo 6 — Reembolse $50 da Captura de $400
O cliente contestou parte da fatura. O reembolso usa o UUID do journal de captura do passo 5, não o ID da retenção:
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" }'
O reembolso debita o destino (carteira de contas a pagar) e credita de volta a carteira de origem (Fornecedor 03) — o ledger consulta a retenção original para encontrar a carteira de origem. A linhagem é preservada ao longo da cadeia de reversão.
Saldo: posted 550 / held 100 / available 450.
Você pode reembolsar parcialmente múltiplas vezes até que o valor cumulativo reembolsado seja igual ao valor capturado. Uma captura totalmente reembolsada rejeita reembolsos adicionais.
Passo 7 — Cancele (Void) a Retenção Restante de $100
A fatura está resolvida ($400 capturados, $50 reembolsados). Libere os $100 restantes:
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 '{}'
Saldo: posted 650 / held 0 / available 650.
Posição final do Fornecedor 03: $1.000 recebidos − $400 capturados + $50 reembolsados = $650. Cada dólar rastreável até o wire original.
Passo 8 — Transfira Entre Carteiras
Mova $200 do Fornecedor 03 para o Fornecedor 07 (mesma moeda):
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\"
}"
A resposta é um journal kind: transfer com duas entries. Transferências são apenas na mesma moeda — para conversões entre ativos, use POST /fx-conversions com os campos explícitos rate e rate_source (ambos são ecoados de volta no metadata do journal para auditoria).
Lendo o Ledger
Liste cada débito e crédito de uma carteira, do mais recente para o mais antigo (paginado, 50 por página; filtre por asset, journal_kind e intervalo de datas):
curl -sS "$WALLET_HOST/organizations/$TENANT/wallets/$VENDOR_03_ID/entries?asset=USD" \ -H "X-API-KEY: $API_KEY"
Cada entry inclui lineageRef — os IDs de origem que financiaram aquele movimento específico — permitindo rastrear qualquer débito para trás e provar de onde veio cada dólar.
Consulte uma retenção (hold) com seu histórico de capturas:
curl -sS "$WALLET_HOST/organizations/$TENANT/holds/$HOLD_ID" \ -H "X-API-KEY: $API_KEY"
Não há endpoint de listagem para retenções — armazene os IDs de retenção (e suas expirações) no seu próprio sistema conforme os cria.
Veja a Referência da API para a tabela completa de endpoints, códigos de erro e detalhes de idempotência.
