---
updatedAt: 2026-09-30T17:50:34.235Z
---

Fetch the complete documentation index at: https://docs.avvio.xyz/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Create a payout batch

`POST https://api.avvio.xyz/business/api/v1/payments/organizations/{orgId}/payouts/batches`

Submit up to 1,000 payouts as one run.

Mass payouts. Each line of `items` is exactly a `POST /payouts` body;
the batch validates every line first (nothing is priced or debited),
then turns the valid lines into ordinary payouts.

`202` means **received**, not paid: poll `GET .../batches/{batchId}` or
subscribe to the `payout_batch.*` webhooks. With `autoCommit: true`
(the default) a run with zero validation errors proceeds straight to
creation. When your organization requires approvals, `autoCommit` is
forced to `false` (and echoed back as `false`) so the run waits for
`POST .../confirm`. If any line fails validation, or `autoCommit` is false, the batch holds at `awaiting_confirmation`: read
`GET .../batches/{batchId}/items?status=invalid`, then either
`POST .../confirm` to proceed with the valid lines or
`POST .../cancel` to stop the run.

The batch tracks creation. Once a line is `created` it carries a
`payoutId` and that payout lives the ordinary payout lifecycle: `payout.*` webhooks, `GET /orders/{payoutId}`, the event feed. A batch
that reads `completed` is a run whose every line was resolved, not a
claim that the money has settled.

The `Idempotency-Key` covers the run. A submit loop that dies and
resubmits the same file under the same key gets the same batch back, never a second payroll.
A `500 PAYOUT_OUTCOME_UNKNOWN` means the run may exist: list batches
by `externalReferenceId` before submitting again, and never under a
new key.

Batch submission has its own rate limit: 30 per minute.

## Parameters

- `orgId` (path, required) — The opaque organization id issued to you, normally CUID-shaped (for example `cmsx…`). It is not an `org_`-prefixed alias. Pass it unchanged in every organization-scoped path.
- `Idempotency-Key` (header, required) — A unique value per logical operation, 1-255 chars of `A-Z a-z 0-9 _ . : -`. Reuse it to retry. Same key with the same body replays the stored response; same key with a *different* body is a `409`, because answering with the first call's result would hand you a receipt for a payout you did not request. A `4xx` releases the key, so you can fix the body and reuse it. **Reuse it; do not generate one per attempt.** A key minted per attempt defeats replay entirely: every retry looks like a new request, so every retry pays. We also watch for an identical body arriving under a *different* key within 15 minutes and refuse it with `DUPLICATE_REQUEST_DETECTED`. Records are kept for 7 days. That is a retention window only: there is no path where an expired key is re-executed.
- `X-Allow-Duplicate` (header) — Set to `true` to send a request that is byte-identical to one you sent within the last 15 minutes under a different key. `true` is the only accepted value. It switches off the guard that catches a retry arriving under a fresh key, so send it only when you mean to pay twice.

## Example

```bash
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/batches" \
  -H "x-api-key: $AVVIO_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "content-type: application/json" \
  -d '{
        "externalReferenceId": "<externalReferenceId>",
        "items": "<items>"
      }'
```

## Responses

- `202` — The batch, in `received`. Nothing has been validated yet, let alone paid. A replayed idempotent request returns the original and sets the `Idempotency-Replayed` response header.
- `400` — Validation of the envelope itself (a malformed line, too many lines, a bad `externalReferenceId`), or a missing or malformed `Idempotency-Key` (`IDEMPOTENCY_KEY_REQUIRED`, `IDEMPOTENCY_KEY_INVALID`).
- `401` — The key was refused. Nothing ran. - `UNAUTHORIZED`: missing, invalid or revoked, or a key on a route that does not accept one. - `KEY_EXPIRED`: the key passed the expiry it was issued with. Issue a new one; an expired key cannot be rotated. - `KEY_IP_NOT_ALLOWED`: the key is pinned to source addresses and this request came from another.
- `403` — A valid key that may not make this write. Nothing was changed. - `MASS_PAYOUTS_DISABLED`: batch submission is off for your organization. `GET /policy` lists `mass_payouts` in `features` when it is on; contact support to enable it. - `FORBIDDEN`, `ACCOUNT_BLOCKED`, `LIVE_KEY_ORG_NOT_APPROVED`, `INSUFFICIENT_SCOPE`: as on every write (`ForbiddenWrite`).
- `409` — `PAYOUT_BATCH_DUPLICATE_REFERENCE`: a batch with this `externalReferenceId` already exists. The run id is unique per organization, which is the guard against a submit job that crashed and re-ran with a fresh key. `originalBatchId` names the existing run; **nothing was submitted**. Or an idempotency conflict (`IDEMPOTENCY_KEY_CONFLICT`, `IDEMPOTENCY_KEY_REQUEST_IN_PROGRESS`, `PAYOUT_OUTCOME_UNKNOWN` on a replay, `DUPLICATE_REQUEST_DETECTED`; see `MoneyIdempotencyConflict`).
- `429` — Too many requests. The default ceiling is **100 requests per minute per API credential** on a 60-second window. High-volume payout and reconciliation routes declare a 600/minute override, and batch submission a 30/minute ceiling. A separate 2,000/minute per-source-IP abuse ceiling always applies. Obey `Retry-After`; it is in seconds and is authoritative. A 429 means the request was refused before the handler ran. Retry reads normally; retry an idempotent mutation with its same `Idempotency-Key`.
- `500` — `PAYOUT_OUTCOME_UNKNOWN`: the call failed after the money may have moved, and we cannot yet say whether it did. **Do not retry with a new `Idempotency-Key`**; that is how a payment goes out twice. Look the outcome up first (the operation says where), or replay the same key, which answers `409 PAYOUT_OUTCOME_UNKNOWN` until we resolve it. Send support the `requestId`. Every other `500` is `INTERNAL`: nothing was recorded under the key, and retrying with the same key is safe.

Machine contract: [partner-payouts.openapi.yaml](/partner-payouts.openapi.yaml), operation `createPayoutBatch`.
