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
| Method | Endpoint | Purpose |
|---|---|---|
POST | /v2/payouts/quotes | Validate bank details and obtain a time-limited payout quote |
POST | /v2/payouts | Create and immediately start a direct payout |
POST | /v2/payouts/status/batch | Query 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.
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.
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_amountis1000.00.sending_amount = receiving_amount + transfer_fee_amount + bank_fee_amount.transfer_fee_amount = sending_amount × fee_rate_decimal; Infini grosses upsending_amountuntil 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.
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_amountis1000.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.
sending_amountis the total amount debited from your Infini USD balance.receiving_amountis the amount sent to the recipient's bank account.transfer_fee_amountis the Infini service fee for processing the payout. It does not include any bank or rail fee.bank_fee_amountis the banking fee associated with the selected bank and payout rail, such as SWIFT, ACH, or wire.billing.total_feeis the sum of all fee components and is provided in the itemized billing breakdown.fee_rate_decimalis the quoted fee rate, andrateis the exchange rate.
For the quoted payout, use sending_amount as the outgoing amount in your reconciliation records.
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.
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.
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=truefor 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.
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.
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.
| HTTP | code | data.error | Returned by | Retryable | Meaning and action |
|---|---|---|---|---|---|
| 400 | 30013 | invalid_parameter | Quote, Create, Get, List | No | A request field is missing, malformed, unsupported, or inconsistent. Correct data.field and submit again. |
| 401 | 401 | authentication_failed | All endpoints | No | HMAC or application authentication failed. Check the API key, signature, date, digest, and signed request target. |
| 403 | 403 | permission_denied | All endpoints | No | The API key lacks the required permission, the IP policy rejected the request, or the organization cannot access the resource. |
| 403 | 108010 | payout_source_account_not_found | Quote, Create | No | The organization does not have a USD payout account. |
| 404 | 404 | resource_not_found | Create, Get | No | The quote_id or payout_id does not exist for the authenticated organization. |
| 409 | 108012 | idempotency_conflict | Create | No | The client_reference_id was already used with different business data. Use a new reference for a different logical payout. |
| 409 | 108013 | quote_already_used | Create | No | The quote has already been consumed. Request a new quote. |
| 409 | 108014 | quote_not_active | Create | No | The quote is no longer active. Request a new quote. |
| 409 | 108015 | payout_submission_in_progress | Create | Yes | Another payout submission is executing for the organization. Retry with a short randomized delay. |
| 409 | 108011 | payout_source_account_invalid | Quote, Create | No | The USD payout account configuration is inconsistent. Contact Infini support. |
| 422 | 108001 | invalid_fee_mode | Quote | No | The selected amount_mode and fee_paid_by combination is unsupported. |
| 422 | 108002 | quote_expired | Create | Yes | The quote expired. Request a new quote; do not retry the expired quote_id. |
| 422 | 108003 | quote_unavailable | Quote, Create | Yes | Quote calculation or its bound destination snapshot is temporarily unavailable. Request a new quote after a short delay. |
| 422 | 108004 | amount_exceeds_limit | Quote | No | The amount exceeds the supported payout amount. Lower the amount. |
| 422 | 108005 | amount_too_small | Quote | No | sending_amount must be greater than 100 USD. Increase the amount. |
| 422 | 108006 | insufficient_balance | Create | No | The organization's available USD balance is insufficient for sending_amount. Add funds before creating another payout. |
| 422 | 108007 | daily_limit_exceeded | Create | No | The payout would exceed the account's shared daily withdrawal limit. Wait for the next limit window or lower the amount. |
| 503 | 108009 | payout_service_unavailable | Create | Yes | Payout execution or provider liquidity is temporarily unavailable. Retry later with the same client_reference_id for the same logical payout. |
| 500 | 500 | internal_error | All endpoints | Yes | An unexpected internal error occurred. Retry safely; if it persists, contact Infini support with the request ID. |
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.
| Payout status | Meaning |
|---|---|
processing | Execution started and final settlement is pending |
completed | The banking partner reported payout completion at the latest known state |
failed | Payout 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.
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_amountis the amount reported as returned by the banking partner.refund_amountis the amount actually credited back to the organization's balance.retained_feeis the original payout fee that was not refunded.returned_atis 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.