Inyo

API Reference

Every endpoint is under /organizations/{tenant}/ on the wallet API host (https://{FQDN}). All requests require the X-API-KEY header. All POSTs require an Idempotency-Key header.


Wallets

MethodPathDescription
GET/walletsList your tenant's wallets
POST/walletsCreate a wallet (owner_type, owner_ref, name)
GET/wallets/{id}Fetch a wallet
GET/wallets/{id}/balancePer-asset balances (?asset=USD optional)
GET/wallets/{id}/entriesLedger entries — paginated (50 per page), filterable by asset, journal_kind, and date range

Wallets created through the API always have kind: operational. Other account kinds (hold sub-accounts, suspense, external source) are system-managed and appear only in ledger entries.

Money Movement

MethodPathDescription
POST/depositsRecord an external source and split it across wallets atomically
POST/holdsPreauthorize (place a hold)
GET/holds/{id}Fetch a hold plus its capture history
POST/holds/{id}/captureCapture part or all of a hold into a destination account
POST/holds/{id}/voidVoid the remaining (uncaptured) portion of a hold
POST/captures/{captureJournalUuid}/refundRefund against a specific capture (partial allowed)
POST/transfersSame-asset transfer between wallets
POST/fx-conversionsCross-asset conversion with explicit rate and rate_source

Journal kinds: deposit, preauth, capture, void, refund, transfer, fx_conversion.

Hold statuses: open, partially_captured, captured, voided. (expired is reserved; expiration is not currently auto-enforced — void expired holds explicitly.)

Deposit source types: wire, ach_debit, card, stablecoin_deposit.


Amounts & Assets

  • All amounts are JSON strings, decimal with up to 8 places. Response amount fields are also strings — never parse them as JSON numbers (precision loss).
  • Supported assets: USD, USDC, EUR, MXN.
  • Balances update atomically with the journal write — a successful response means the balance reflects the operation.

Error Handling

All errors return a JSON body:

{
  "error": "ERROR_CODE",
  "message": "Human-readable explanation."
}
HTTPerrorWhen it happens
400MISSING_IDEMPOTENCY_KEYPOST without an Idempotency-Key header
401UNAUTHORIZEDMissing or invalid X-API-KEY
403FORBIDDENAPI key doesn't match the {tenant} in the URL
404NOT_FOUNDReferenced wallet, hold, or capture doesn't exist (or isn't in your tenant)
409INVALID_HOLD_STATECapturing a voided hold, voiding a captured one, etc.
422VALIDATION_ERRORPayload failed validation — the errors field lists per-field problems
422INSUFFICIENT_FUNDSPreauth or transfer exceeds the wallet's available balance
422INVALID_ASSETUnknown or unsupported asset_code
422INVALID_ACCOUNTReferenced account is not valid for the operation
422LEDGER_IMBALANCESplits don't sum to the source amount, or another double-entry violation
500INTERNAL_ERRORServer-side error — contact support with the timestamp and request details

For VALIDATION_ERROR, the response includes a per-field breakdown:

{
  "error": "VALIDATION_ERROR",
  "message": "The wallet id field must be a uuid.",
  "errors": {
    "wallet_id": ["The wallet id field must be a uuid."]
  }
}

Idempotency

Every POST endpoint requires an Idempotency-Key header. If you send two requests with the same key, the second returns the exact response of the first — no duplicate journal is created.

Why it matters: network failures are silent. A request that times out after sending but before receiving the response leaves you unsure whether the operation succeeded. Retrying with the same key is always safe.

How to name keys: any unique string up to 191 characters. Best practice is a stable, meaningful identifier per operation:

  • deposit-BANK-WIRE-98765432 for a specific wire
  • capture-INV-4521 for a specific invoice
  • refund-INV-4521-partial-1 for a specific refund

Never reuse a key across different operations. Reusing invoice-4521-refund for a $50 refund and later for a $100 refund returns the $50 response the second time — the $100 refund never happens.

Keys are stored per journal, scoped to your tenant, and never expire.


Traceability

  • Every journal response includes parentJournalId when it reverses or continues another operation — follow the chain refund → capture → preauth for a full audit trail.
  • Every entry includes lineageRef — the source IDs (FIFO-consumed) that funded that specific movement.
  • Deposit journals include the created source's UUID in metadata.source_uuid for cross-referencing.

Roadmap Notes

  • Webhooks for balance changes are on the roadmap — poll balances or entries in the meantime.
  • Marketplace-style escrow (visible to both parties) is planned as a separate flavor from card-like holds — contact Inyo if you need it.