Skip to content

Five calls take a test key to a payout in Mexico. Everything runs in the sandbox, where no real money moves. The Mexico recipe runs the same calls one step at a time, with every response, the corridor’s fields and a failure on purpose.

export AVVIO_API_KEY=avvio_test_…
export AVVIO_ORG_ID=cmsx… # a cuid, not an org_ prefix
export AVVIO_BASE_URL=https://api.avvio.xyz/business/api/v1

For the Node tabs, install the SDK and create the client once; every Node tab below uses this avvio:

// npm i @avvio/payments
import { PayoutsClient } from '@avvio/payments';
// Reads AVVIO_API_KEY, AVVIO_ORG_ID and AVVIO_BASE_URL from the environment.
const avvio = new PayoutsClient();

Send the key as x-api-key on every request; there is nothing to sign. Production uses the same base URL, and the avvio_test_ prefix keeps these calls in your sandbox.

Your serverAvvio corePayment railBank1 GET /recipients/{org}/corridorsCorridor field specs3 POST /recipients/{org}destinationAccountId4 POST /payments/…/payoutsIdempotency-Key requiredDebits USD balanceDispatchSettlepayoutId, status: pendingWebhook: payout.completedwithin ~1 minute5 GET /events?since= (reconcile)
Steps 1 and 3 register the recipient once. Step 4 moves money. Step 5 keeps your ledger in sync.

  1. POST /payments/organizations/{orgId}/sandbox/fund
    IDEMPOTENCY_KEY=$(uuidgen) # new key per call; reuse it only to retry this exact request
    curl -s -X POST "$AVVIO_BASE_URL/payments/organizations/$AVVIO_ORG_ID/sandbox/fund" \
    -H "x-api-key: $AVVIO_API_KEY" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -H "content-type: application/json" \
    -d '{
    "amount": "5000.00"
    }'

    Payouts draw on your USD balance. A 200 also proves the key works.

  2. POST /recipients/{orgId}
    IDEMPOTENCY_KEY=$(uuidgen) # new key per call; reuse it only to retry this exact request
    curl -s -X POST "$AVVIO_BASE_URL/recipients/$AVVIO_ORG_ID" \
    -H "x-api-key: $AVVIO_API_KEY" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -H "content-type: application/json" \
    -d '{
    "type": "individual",
    "name": "María González",
    "country": "MX",
    "externalId": "cust42_maria",
    "endUserId": "customer_42",
    "method": {
    "kind": "fiat",
    "currency": "MXN",
    "recipientDetails": {
    "clabeNumber": "012180000080004471"
    }
    }
    }' | tee response.json
    export DESTINATION_ACCOUNT_ID=$(jq -r .method.destinationAccountId response.json)

    Mexico needs one field, clabeNumber. Each tab saves method.destinationAccountId as DESTINATION_ACCOUNT_ID: you pay that id, not the recipient id. Other countries need other fields (Register recipients).

  3. 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"
    POST /payments/organizations/{orgId}/payouts
    IDEMPOTENCY_KEY=$(uuidgen) # new key per call; reuse it only to retry this exact request
    curl -s -X POST "$AVVIO_BASE_URL/payments/organizations/$AVVIO_ORG_ID/payouts" \
    -H "x-api-key: $AVVIO_API_KEY" \
    -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
    -H "content-type: application/json" \
    -d '{
    "amount": "200.00",
    "destinationAccountId": "'"$DESTINATION_ACCOUNT_ID"'",
    "amountLeg": "source",
    "expectDestination": "3384.65",
    "maxDriftBps": 200,
    "reference": "ZZ-2026-0042",
    "endUser": {
    "id": "customer_42"
    }
    }'

    Send the amount you showed as expectDestination. If the price drifts more than maxDriftBps (default 2%) from it, the call fails with RATE_DRIFT_EXCEEDED and nothing is sent. Keep payoutId for step 4. Sending the same payout again under a new key within 15 minutes (running a second tab, say) is refused with 409 DUPLICATE_REQUEST_DETECTED and nothing is sent; change reference to send another.

  4. payoutId=sbx_pay_… # from step 3's response
    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"

    In the sandbox the payout reaches completed 10 to 20 seconds after you create it. To follow many payouts, use webhooks and the event feed rather than polling: GET …/orders/{payoutId} shares the default 100-requests-a-minute limit, so poll one payout no more than about every 5 seconds. The recipe shows each response and makes a payout fail on purpose; run that before you go live.


The OpenAPI spec generates a client in any language, and our CI checks the spec’s schemas against live responses (it does not build or run a generated client). Swap -g python for java, go, ruby, csharp or php.

npx @openapitools/openapi-generator-cli generate \
-i partner-payouts.openapi.yaml -g python -o ./avvio --package-name avvio_payouts

Was this page helpful?