Inyo

Guía de Integración

Esta guía recorre un escenario B2B real de principio a fin.

Escenario: ACME Corp envía por transferencia bancaria $10,000 USD a Inyo. Ese dinero se divide entre 10 sub-billeteras — una por proveedor — con $1,000 cada una. El proveedor 03 luego ejecuta una preautorización de $500 por una factura pendiente, captura $400 de ella (la factura llegó por un monto menor al esperado), reembolsa $50 al cliente y finalmente anula la retención restante.

Cada dólar permanece trazable hasta la transferencia bancaria original.

Para los ejemplos siguientes, exporta esto una sola vez:

export WALLET_HOST=https://{FQDN}
export TENANT=acme-corp
export API_KEY=your-tenant-api-key

Paso 1 — Crear las Billeteras

Necesitas 11 billeteras: una billetera de "tesorería" que recibe la transferencia y 10 billeteras de proveedores que reciben la división.

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"
  }'

Respuesta:

{
  "id": "f64c49bd-47a3-4cc6-b9d4-265d1fe5ba65",
  "name": "ACME Treasury",
  "kind": "operational",
  "ownerType": "company",
  "ownerRef": "acme-corp",
  "createdAt": "2026-07-14T04:01:33+00:00"
}

Repite para las 10 billeteras de proveedores con valores diferentes de owner_ref y name, y almacena los UUIDs devueltos. Más abajo, $VENDOR_03_ID se refiere a la billetera del proveedor 03.

Nombrar tu Idempotency-Key: usa algo significativo y único por operación (p. ej., acme-vendor-03-2026-07-14). Reutilizar la misma clave devuelve la respuesta original en lugar de crear un duplicado — es seguro reintentar ante fallas de red.


Paso 2 — Depositar y Dividir la Transferencia de $10k

El banco de ACME confirmó que la transferencia llegó a tu cuenta de liquidación. Regístrala en el libro contable y divídela entre los 10 proveedores en una sola operación 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"}
    ]
  }'

Respuesta (abreviada — la respuesta completa tiene los 11 movimientos):

{
  "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"
}

Qué acaba de pasar:

  1. Se creó un registro de origen (source) que documenta la transferencia bancaria.
  2. Se escribió un journal de tipo deposit con 11 movimientos: un débito de $10,000 contra la cuenta de origen externo ("dinero que entra desde fuera del libro contable") y 10 créditos de $1,000 contra las billeteras de los proveedores.
  3. Cada movimiento de crédito lleva un lineageRef que apunta al origen.
  4. La suma de los débitos es igual a la suma de los créditos — se cumple el invariante de la partida doble.

El source.amount debe ser igual a la suma de las divisiones — de lo contrario, la API devuelve un error LEDGER_IMBALANCE.

Para mantener una porción de la transferencia en una cuenta suspense del sistema (comisiones de liquidación, flotante operativo), pasa gateway_suspense_amount en el nivel superior. Entonces sum(splits) + gateway_suspense_amount debe ser igual a source.amount.

Valores aceptados para source_type: wire, ach_debit, card, stablecoin_deposit.


Paso 3 — Verificar los 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"
  }
]

El proveedor 03 tiene $1,000 disponibles. Si una billetera mantiene múltiples activos, filtra con ?asset=USD — esto devuelve un array de un solo elemento (o vacío si no hay saldo en ese activo).


Paso 4 — Preautorizar $500

El proveedor 03 tiene una factura por llegar. Reserva $500 antes de conocer el monto exacto para que el dinero no pueda gastarse en otra cosa:

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\"
  }"

Respuesta:

{
  "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": []
}

Guarda el id de la retención — capturarás/anularás contra él. El saldo ahora muestra:

[
  { "assetCode": "USD", "posted": "500.00000000", "held": "500.00000000", "available": "0.00000000" }
]
  • posted bajó de $1,000 a $500 — la preautorización debitó la billetera y acreditó una subcuenta de retención por billetera.
  • held muestra la reserva de $500.
  • available es $0 — cada dólar restante está reservado.

expires_at es informativo. La retención registra la expiración, pero nada anula automáticamente las retenciones expiradas. Haz seguimiento de los IDs de retención y sus expiraciones por tu lado y anúlalas explícitamente cuando venzan.


Paso 5 — Capturar $400 de la Retención de $500

La factura llegó por $400. Captura ese monto hacia una cuenta de destino — normalmente una billetera de cuentas por pagar/liquidación que creaste 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\"
  }"

Respuesta:

{
  "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] }
  ]
}

Guarda el id del journal (b092a884-...) — este es el UUID del journal de captura, requerido para reembolsar.

Saldo después de la captura: posted 500 / held 100 / available 400. Los $400 fueron liberados de la retención y movidos al destino; los $100 que estaban reservados por encima del monto de la factura permanecen retenidos.

Capturas parciales: puedes capturar múltiples veces contra la misma retención hasta que el monto acumulado sea igual al monto de la retención. El estado transiciona open → partially_captured → captured. Una vez completamente captured, no son posibles más capturas ni anulaciones — solo reembolsos.


Paso 6 — Reembolsar $50 de la Captura de $400

El cliente disputó parte de la factura. El reembolso usa el UUID del journal de captura del paso 5, no el ID de la retención:

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" }'

El reembolso debita el destino (billetera de cuentas por pagar) y acredita de vuelta a la billetera de origen (proveedor 03) — el libro contable consulta la retención original para encontrar la billetera de origen. El linaje se preserva a través de la cadena de reversión.

Saldo: posted 550 / held 100 / available 450.

Puedes reembolsar parcialmente múltiples veces hasta que el monto acumulado reembolsado sea igual al monto capturado. Una captura completamente reembolsada rechaza reembolsos adicionales.


Paso 7 — Anular la Retención Restante de $100

La factura está liquidada ($400 capturados, $50 reembolsados). Libera los $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.

Posición final del proveedor 03: $1,000 recibidos − $400 capturados + $50 reembolsados = $650. Cada dólar trazable hasta la transferencia bancaria original.


Paso 8 — Transferir Entre Billeteras

Mueve $200 del proveedor 03 al proveedor 07 (misma moneda):

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\"
  }"

La respuesta es un journal kind: transfer con dos movimientos. Las transferencias son solo de la misma moneda — para conversiones entre activos, usa POST /fx-conversions con los campos explícitos rate y rate_source (ambos se devuelven en el metadata del journal para auditoría).


Lectura del Libro Contable

Lista cada débito y crédito de una billetera, del más reciente al más antiguo (paginado, 50 por página; filtra por asset, journal_kind y rango de fechas):

curl -sS "$WALLET_HOST/organizations/$TENANT/wallets/$VENDOR_03_ID/entries?asset=USD" \
  -H "X-API-KEY: $API_KEY"

Cada movimiento incluye lineageRef — los IDs de origen que financiaron ese movimiento específico — lo que te permite rastrear cualquier débito hacia atrás para demostrar de dónde vino cada dólar.

Obtén una retención con su historial de capturas:

curl -sS "$WALLET_HOST/organizations/$TENANT/holds/$HOLD_ID" \
  -H "X-API-KEY: $API_KEY"

No hay un endpoint de listado para las retenciones — almacena los IDs de retención (y sus expiraciones) en tu propio sistema a medida que las creas.

Ver la Referencia de la API para la tabla completa de endpoints, códigos de error y detalles de idempotencia.