Skip to content

A payout moves USD from your balance to a recipient’s bank account in their currency. One call prices and sends it.

  1. Optionally show a price with GET /payments/organizations/{orgId}/rates.
  2. Send with POST …/payouts.
  3. Read the outcome from GET …/orders/{payoutId} or the payout.* webhooks.

To lock a rate before money moves, you can quote, then accept instead of step 2. That path is closed once your organization has caps or approvals; use POST /payouts and approvals for review.

  1. GET /payments/organizations/{orgId}/rates
    curl -s "$AVVIO_BASE_URL/payments/organizations/$AVVIO_ORG_ID/rates?from=USD&to=MXN&amount=200.00" \
    -H "x-api-key: $AVVIO_API_KEY"

    The price needs no recipient and creates nothing. Keep destinationAmount.amount as the next step’s expectDestination.

  2. POST /payments/organizations/{orgId}/payouts
    x-api-key: avvio_live_…
    Idempotency-Key: c94b21fa-36e2-411a-9f5b-91f81d11234a
    Content-Type: application/json
    {
    "amount": "200.00",
    "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5",
    "expectDestination": "3384.65",
    "maxDriftBps": 200,
    "reference": "PAYROLL-2026-0042",
    "endUser": {
    "id": "customer_42",
    "name": "Ana López"
    }
    }
    {
    "payoutId": "sbx_pay_7c1c0f2e-6d9b-4a3e-9d1a-2f4b8c9e0a11",
    "status": "pending",
    "sourceAmount": { "currency": "USD", "amount": "200.00" },
    "destinationAmount": { "currency": "MXN", "amount": "3384.65" },
    "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5",
    "fee": { "currency": "USD", "amount": "1.02" },
    "rate": "16.923250",
    "reference": "PAYROLL-2026-0042",
    "endUser": { "id": "customer_42", "name": "Ana López" },
    "createdAt": "2026-08-20T14:03:11.000Z",
    "updatedAt": "2026-08-20T14:03:11.000Z",
    "completedAt": null
    }

    An accepted payout answers 200 with status: "pending". The fee is deducted from the send, so destinationAmount = sourceAmount × rate, where rate is the effective rate after the fee, not the mid-rate from /rates. fee is null when the network has not disclosed one.

    expectDestination is the destination amount you showed the user. maxDriftBps is how far the rate may slip, in basis points (default 200, or 2%). Drift is checked only when you send expectDestination. A payout that drifts further is refused with 400 RATE_DRIFT_EXCEEDED and nothing is debited.

    INR, GHS, CNY and BRL payouts need purposeOfPayment: on POST /payouts, or, on the two-step path, on POST …/quotes/offramp (the accept call takes no purposeOfPayment). Pick a value from the currency’s list; we never default one:

    GET /payments/organizations/{orgId}/payment-reasons?currency=MXN

    The response is { payment_reasons: [...], default? }. A missing or unlisted value is refused with 400 VALIDATION_ERROR before anything is priced. So is paymentReason on POST /payouts, where it does not exist; on POST …/quotes/accept it is an optional free-text field, and purposeOfPayment there is refused as unknown.

    Above your organization’s approval threshold, POST /payouts answers 202 and nothing is priced or sent:

    { "status": "pending_approval", "approvalId": "cmf3k2xf90008q8b7q4r6s8tu", "requiredApprovals": 2, "expiresAt": "2026-09-04T10:00:00.000Z" }

    Wait for payout_approval.executed, which carries the payoutId. A replay of the same key returns the same 202 (Approvals).

    Refusal When
    422 PAYOUT_LIMIT_EXCEEDED Over one of your USD caps: per payout, per day, or per end user per day
    422 PAYOUT_REFUSED Screening refused it
    400 INSUFFICIENT_BALANCE Larger than your USD balance; no quote is used up

    The per-end-user cap counts against endUser.id and makes it required. Caps and screening apply to POST /payouts, batches and payout links. Quote-then-accept is refused outright with 403 USE_POST_PAYOUTS once your organization has any cap or approval threshold. Errors lists the rest.

  3. GET /payments/organizations/{orgId}/orders/{payoutId}
    curl -s "$AVVIO_BASE_URL/payments/organizations/$AVVIO_ORG_ID/orders/$payoutId" \
    -H "x-api-key: $AVVIO_API_KEY"

    The read is authoritative even when a webhook went missing. Track status & failures covers every status.

A quote locks the rate until its expires_at. Nothing is debited until you accept. Creating a quote takes no Idempotency-Key; accepting it does.

POST /payments/organizations/{orgId}/quotes/offramp
x-api-key: avvio_live_…
Content-Type: application/json
{
"amount": "200.00",
"destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5",
"amountLeg": "source"
}

The quote may also carry purposeOfPayment (validated here, for the currencies that need one) and sourceCurrency, the balance currency to debit. This returns a snapshot id, a best_quote_id and expires_at (snake case). Accepting moves the money:

POST /payments/organizations/{orgId}/quotes/accept
x-api-key: avvio_live_…
Idempotency-Key: <uuid>
Content-Type: application/json
{
"snapshotId": "sbx_quote_eyJkIjoic2J4X2FjY3RfTVhOXzQ0NzFfYWU2NmNiYzUiLCJzIjoiMjAwLjAwIn0",
"quoteId": "sbx_quote_eyJkIjoic2J4X2FjY3RfTVhOXzQ0NzFfYWU2NmNiYzUiLCJzIjoiMjAwLjAwIn0",
"type": "OFFRAMP",
"reference": "INV-1092"
}

The accept body also takes an optional paymentReason. Its reference allows only letters, digits, spaces, : and -.

amount is a decimal string, never a JSON number: up to six decimals on a quote, two on a one-call payout. Quote-accept and one-call payouts refuse near-duplicates unless you send X-Allow-Duplicate (Idempotency).

Payouts are priced from the amount you send (amountLeg: "source"). To fix what arrives instead, set amountLeg: "destination":

{
"amount": "5000.00",
"amountLeg": "destination",
"destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5"
}

Check capabilities.exactOutput on GET /recipients/{orgId}/corridors first. When it is false, this returns 400 EXACT_OUTPUT_UNSUPPORTED.

fees.payout on GET …/policy is the schedule your routing states before a quote. Where published, the fee is fixedUsd + sourceAmount × bps / 10000, rounded to the cent and deducted before conversion.

Field Holds
bps Basis points of the send, for every currency not in byCurrency
fixedUsd The fixed part, in USD
byCurrency Overrides by destination currency
note Prose; don’t branch on it

A null number means “not published before a quote”, never zero. Some routings price the whole fee inside each quote, and one adds the network’s charge on top of bps. The binding figure is always fee on the quote and payout, which matches its events and /balance_transactions row.

Was this page helpful?