Inyo

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:

  1. Uma linha de origem (source) foi criada registrando o wire.
  2. Um journal do tipo deposit foi 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.
  3. Toda entry de crédito carrega um lineageRef apontando para a origem.
  4. 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_amount no nível superior. Nesse caso, sum(splits) + gateway_suspense_amount deve ser igual a source.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" }
]
  • posted caiu de $1.000 para $500 — a pré-autorização debitou a carteira e creditou uma sub-conta de retenção por carteira.
  • held mostra 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 id do 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.