Skip to content

The Fiat Payout API lets an organization send USD from its Infini USD balance to a third-party bank account.

All endpoints use this prefix:

/v2/payouts

Minimum API surface

MethodEndpointPurpose
POST/v2/payouts/quotesValidate bank details and obtain a time-limited payout quote
POST/v2/payoutsCreate and immediately start a direct payout
POST/v2/payouts/status/batchQuery payout statuses by creation-time range with pagination
GET/v2/payouts/{payout_id}Query current payout status

There is no recipient resource in this release. Bank destination details are submitted with the quote and are immutably bound to quote_id. POST /v2/payouts cannot replace or modify that destination.

Choose the amount to control

When creating a quote, choose whether the recipient amount or your total debit must remain exact. fee_paid_by changes how Infini calculates both amounts; it is not a display-only field. The first release supports the following two options.

Recipient receives an exact amount

Use this option when the recipient must receive a specific amount, such as an invoice payment.

{
  "amount": "1000.00",
  "amount_mode": "receive",
  "fee_paid_by": "PAYER"
}

The recipient receives exactly 1000.00 USD. PAYER uses an external fee deduction: payout fees are added to the amount debited from your Infini USD balance.

  • receiving_amount is 1000.00.
  • sending_amount = receiving_amount + transfer_fee_amount + bank_fee_amount.
  • transfer_fee_amount = sending_amount × fee_rate_decimal; Infini grosses up sending_amount until the recipient amount remains exact after all fees.

For example, with a 0.001 transfer-fee rate and a 50.00 USD bank fee, Infini calculates sending_amount=1051.05, transfer_fee_amount=1.05, and receiving_amount=1000.00.

Debit an exact total amount

Use this option when the payout must not debit more than a fixed budget.

{
  "amount": "1000.00",
  "amount_mode": "send",
  "fee_paid_by": "BENEFICIARY"
}

Infini debits exactly 1000.00 USD. BENEFICIARY uses an internal fee deduction: payout fees are deducted from that amount, so the recipient receives less.

  • sending_amount is 1000.00.
  • receiving_amount = sending_amount - transfer_fee_amount - bank_fee_amount.
  • transfer_fee_amount = sending_amount × fee_rate_decimal.

For example, with a 0.001 transfer-fee rate and a 50.00 USD bank fee, Infini calculates sending_amount=1000.00, transfer_fee_amount=1.00, and receiving_amount=949.00.

Use only the field pairs shown above. Other amount_mode and fee_paid_by combinations are not supported in the first release. If you change fee_paid_by, request a new quote; quote_id locks the fee responsibility and calculated amounts used by the payout.

Read the quote amounts

  • sending_amount is the total amount debited from your Infini USD balance.
  • receiving_amount is the amount sent to the recipient's bank account.
  • transfer_fee_amount is the Infini service fee for processing the payout. It does not include any bank or rail fee.
  • bank_fee_amount is the banking fee associated with the selected bank and payout rail, such as SWIFT, ACH, or wire.
  • billing.total_fee is the sum of all fee components and is provided in the itemized billing breakdown.
  • fee_rate_decimal is the quoted fee rate, and rate is the exchange rate.

For the quoted payout, use sending_amount as the outgoing amount in your reconciliation records.

Direct execution

API payouts do not enter the dashboard approval workflow. POST /v2/payouts runs the authoritative pre-execution checks and starts execution immediately.

A bank payout is asynchronous. A successful create response with status=processing means the request passed Infini's checks and execution started. It does not mean the recipient bank account has already been credited.

completed means the payout completed successfully. A receiving bank can still reject and return a previously completed transfer. The original payout remains completed; an independent compensating refund is exposed through the optional return object.

Quote versus execution checks

POST /v2/payouts/quotes validates the destination and performs preliminary route, amount, compliance, and limit checks. It returns a time-limited snapshot of the destination, amounts, rate, fees, and fee responsibility. A quote:

  • does not reserve organization balance;
  • does not approve or create a payout;
  • cannot be reused after expiry;
  • does not guarantee that current limits, compliance state, balance, or provider availability will remain unchanged.

POST /v2/payouts repeats every execution-critical check immediately before creating the canonical payout, debiting the balance, and starting bank execution.

Idempotency

client_reference_id is merchant-scoped and identifies one logical payout:

  • a retry with the same reference and identical business request returns the existing resource;
  • the response sets is_duplicate=true for an idempotent payout retry;
  • reusing a reference with different business data returns 409 idempotency_conflict.

The quote_id and all payout-defining fields participate in the idempotency comparison.

Concurrent submissions

An organization can have only one POST /v2/payouts submission entering execution at a time. If another payout submission is already in progress, the API rejects the concurrent request before consuming the quote, creating a payout, or debiting the organization's balance:

{
  "code": 108015,
  "message": "Another payout submission is in progress. Please retry shortly.",
  "data": {
    "error": "payout_submission_in_progress",
    "field": null,
    "retryable": true
  }
}

Retry after a short randomized delay. For the same logical payout, keep the original client_reference_id so that idempotency remains effective.

Error codes

Application errors use the following envelope:

{
  "code": 108006,
  "message": "Insufficient payout balance",
  "data": {
    "error": "insufficient_balance",
    "field": "amount",
    "retryable": false
  }
}

Use code or data.error for program control. Do not match on message, which is intended for troubleshooting. When data.retryable=true, retry only after the indicated transient condition is resolved. HMAC requests rejected directly by the gateway can return only a message field because they do not reach the application.

Fiat Payout-specific business error codes use the reserved 108001-108099 range. The HTTP status and response-body code serve different purposes: use the HTTP status for the error category and code for the stable business reason.

HTTPcodedata.errorReturned byRetryableMeaning and action
40030013invalid_parameterQuote, Create, Get, ListNoA request field is missing, malformed, unsupported, or inconsistent. Correct data.field and submit again.
401401authentication_failedAll endpointsNoHMAC or application authentication failed. Check the API key, signature, date, digest, and signed request target.
403403permission_deniedAll endpointsNoThe API key lacks the required permission, the IP policy rejected the request, or the organization cannot access the resource.
403108010payout_source_account_not_foundQuote, CreateNoThe organization does not have a USD payout account.
404404resource_not_foundCreate, GetNoThe quote_id or payout_id does not exist for the authenticated organization.
409108012idempotency_conflictCreateNoThe client_reference_id was already used with different business data. Use a new reference for a different logical payout.
409108013quote_already_usedCreateNoThe quote has already been consumed. Request a new quote.
409108014quote_not_activeCreateNoThe quote is no longer active. Request a new quote.
409108015payout_submission_in_progressCreateYesAnother payout submission is executing for the organization. Retry with a short randomized delay.
409108011payout_source_account_invalidQuote, CreateNoThe USD payout account configuration is inconsistent. Contact Infini support.
422108001invalid_fee_modeQuoteNoThe selected amount_mode and fee_paid_by combination is unsupported.
422108002quote_expiredCreateYesThe quote expired. Request a new quote; do not retry the expired quote_id.
422108003quote_unavailableQuote, CreateYesQuote calculation or its bound destination snapshot is temporarily unavailable. Request a new quote after a short delay.
422108004amount_exceeds_limitQuoteNoThe amount exceeds the supported payout amount. Lower the amount.
422108005amount_too_smallQuoteNosending_amount must be greater than 100 USD. Increase the amount.
422108006insufficient_balanceCreateNoThe organization's available USD balance is insufficient for sending_amount. Add funds before creating another payout.
422108007daily_limit_exceededCreateNoThe payout would exceed the account's shared daily withdrawal limit. Wait for the next limit window or lower the amount.
503108009payout_service_unavailableCreateYesPayout execution or provider liquidity is temporarily unavailable. Retry later with the same client_reference_id for the same logical payout.
500500internal_errorAll endpointsYesAn unexpected internal error occurred. Retry safely; if it persists, contact Infini support with the request ID.

Sensitive bank data

The quote request contains sensitive bank details. Responses return only a masked account number. Clients must not put raw bank-account values in URLs, logs, client_reference_id, statement_reference, or other free-text metadata.

Status and reconciliation

Payout statusMeaning
processingExecution started and final settlement is pending
completedThe banking partner reported payout completion at the latest known state
failedPayout failed before successful bank completion

There is intentionally no approval-pending status for API payouts.

Use POST /v2/payouts/status/batch to reconcile payouts created within an inclusive Unix-second time range. Results are ordered by creation time descending. The endpoint uses page-based pagination: page defaults to 1, page_size defaults to 20, and page_size cannot exceed 100.

Bank return and refund

If the receiving bank later returns a completed payout, status remains completed, failure remains null, and return contains the compensating refund:

{
  "status": "completed",
  "return": {
    "status": "refunded",
    "reason_code": "recipient_bank_rejected",
    "reason": "Beneficiary bank rejected the transfer",
    "returned_amount": "1000.00",
    "refund_amount": "1000.00",
    "retained_fee": "51.00",
    "currency": "USD",
    "returned_at": 1786674271
  }
}
  • returned_amount is the amount reported as returned by the banking partner.
  • refund_amount is the amount actually credited back to the organization's balance.
  • retained_fee is the original payout fee that was not refunded.
  • returned_at is the time the compensating refund was recorded.

For payouts without a bank return, return is null.

Use GET /v2/payouts/{payout_id} for one known payout or POST /v2/payouts/status/batch for time-window reconciliation. Both are authoritative public reconciliation interfaces.