---
updatedAt: 2026-09-30T15:54:20.000Z
---

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.

# Send a batch

A batch submits up to 1,000 payouts in one request, validates every line, then
waits for you to confirm or cancel. [Batch payouts](/products/batch-payouts/)
covers when to use one and its limits.

1. Submit the run with `POST /payments/organizations/{orgId}/payouts/batches`.
2. Watch it with `GET …/batches/{batchId}`.
3. Review the lines with `GET …/batches/{batchId}/items`.
4. Confirm with `POST …/batches/{batchId}/confirm`, or cancel.
5. Join each created payout to your ledger.

## 1. Submit the run

A `202` means received, not paid. Each line of `items` is a `POST /payouts`
body.

```http
POST /payments/organizations/{orgId}/payouts/batches
x-api-key: avvio_live_…
Idempotency-Key: payroll-2026-09-01-run1
Content-Type: application/json

{
  "externalReferenceId": "payroll-2026-09-01",
  "autoCommit": true,
  "items": [
    { "amount": "200.00", "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5", "reference": "PAYROLL-2026-0042" },
    { "amount": "150.00", "destinationAccountId": "sbx_acct_MXN_0002_1b02c77d", "reference": "PAYROLL-2026-0043" }
  ]
}
```

```json
{
  "batchId": "cmf3k2xg00009q8b7v0w2x4yz",
  "externalReferenceId": "payroll-2026-09-01",
  "status": "received",
  "autoCommit": true,
  "counts": { "received": 2, "invalid": 0, "validated": 0, "creating": 0,
              "created": 0, "create_failed": 0, "canceled": 0, "requires_review": 0 },
  "createdAt": "2026-09-01T14:03:11.000Z",
  "updatedAt": "2026-09-01T14:03:11.000Z",
  "completedAt": null
}
```

An unknown field on any line refuses the whole request with `400`. A throttled
submit gets `429` with `Retry-After` in seconds; resend with the same key.

### The run id and the key

`Idempotency-Key` identifies the HTTP attempt: the same key and body within 7
days returns the same batch, with the `Idempotency-Replayed` header
([Idempotency](/idempotency/)). `externalReferenceId` identifies the run
permanently, so it also catches a job that loses its key and re-runs the file
under a fresh one. It is required, 1 to 128 characters of letters, digits,
spaces and `. _ : -`, and unique per organization. Submit accepts
`X-Allow-Duplicate: true`, but the run id must still differ. A second batch
with the same id answers:

```jsonc
// 409
{
  "type": "PAYOUT_BATCH_DUPLICATE_REFERENCE",
  "message": "A batch with externalReferenceId \"payroll-2026-09-01\" already exists (cmf3k2xg00009q8b7v0w2x4yz). NOTHING WAS SUBMITTED. …",
  "originalBatchId": "cmf3k2xg00009q8b7v0w2x4yz"
}
```

If this was a retry, continue from `originalBatchId`. A new run, such as
corrected lines, needs its own id (`payroll-2026-09-01-r2`).
`GET …/batches?externalReferenceId=` finds the run an id already names.

### `autoCommit`

With `true` (the default), a run with no validation errors goes straight to
creation and a run with any error holds at `awaiting_confirmation`. With
`false`, it always holds. When approvals are required, `autoCommit` is forced
off and `confirm` captures the approval.

## 2. Watch it move

```http
GET /payments/organizations/{orgId}/payouts/batches/{batchId}
```

```jsonc
{
  "batchId": "cmf3k2xg00009q8b7v0w2x4yz",
  "externalReferenceId": "payroll-2026-09-01",
  "status": "awaiting_confirmation",
  "autoCommit": true,
  "counts": { "received": 0, "invalid": 3, "validated": 247, "creating": 0,
              "created": 0, "create_failed": 0, "canceled": 0, "requires_review": 0 },
  "estimatedSourceTotal": "49400.00",
  "createdAt": "2026-09-01T14:03:11.000Z",
  "updatedAt": "2026-09-01T14:03:40.000Z",
  "completedAt": null
}
```

`estimatedSourceTotal` is the advisory sum of the valid lines, to compare with
your balance before confirming. It is `null` while validating and when any line
uses `amountLeg: "destination"`.

A batch tracks creation, not settlement
([batch statuses](/status/#batch-payout-status)). On a `failed` batch, `error`
is an object, `{ "code": "VALIDATION_RUN_FAILED", "message": "…" }` (read
`error.code`), and no payout exists; contact support with the
`batchId` instead of resubmitting. Instead of polling, listen for the
`payout_batch.*` [webhooks](/webhooks/). List your runs, newest first:

```http
GET /payments/organizations/{orgId}/payouts/batches?status=&externalReferenceId=&limit=&cursor=
```

`limit` is 1 to 100 (default 50). A foreign cursor or unknown `status` is a
`400`, never an empty page.

## 3. Review the lines

```http
GET /payments/organizations/{orgId}/payouts/batches/{batchId}/items?status=invalid
```

Lines come back in order, each echoing its instruction:

```jsonc
{
  "data": [
    {
      "index": 17,
      "status": "invalid",
      "instruction": { "amount": "200.00", "destinationAccountId": "sbx_acct_MXN_0009_d41d8cd9", "reference": "PAYROLL-2026-0059" },
      "errors": [ { "code": "DESTINATION_ACCOUNT_NOT_FOUND", "message": "…" } ]
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

`limit` is 1 to 1,000 (default 100); the cursor is the last line's `index`.
Error codes match a single `POST /payouts` ([Errors](/errors/)).

`?format=csv` returns the whole run, filtered by `?status=`, with columns
`index, status, payoutId, amount, amountLeg, destinationAccountId, reference,
errorCode, errorMessage` (first error per line only).

### Item statuses

| Status            | Meaning                                                                | Terminal |
| ----------------- | ----------------------------------------------------------------------- | -------- |
| `received`        | Stored, not yet examined                        |          |
| `invalid`         | Failed validation; see `errors`. No payout      | ✓        |
| `validated`       | Waiting for creation or your confirm            |          |
| `creating`        | A quote/accept is in flight                     |          |
| `created`         | A payout exists; `payoutId` is set              | ✓        |
| `create_failed`   | Refused at creation; see `errors`. No payout    | ✓        |
| `canceled`        | Batch canceled before this line was attempted   | ✓        |
| `requires_review` | Outcome unknown; see below                      | ✓        |

### Lines that need review

> [!WARNING]
> A line whose creation died mid-way, or whose accept the network never
> answered, may have paid. It is marked `requires_review` with `OUTCOME_UNKNOWN`
> and never retried. Resubmitting it can pay twice, so contact support with the
> `batchId`. A batch can reach `completed` with such lines, so check `counts`.

## 4. Confirm or cancel

Both take an `Idempotency-Key`, and both act only before the run is committed.

```http
POST /payments/organizations/{orgId}/payouts/batches/{batchId}/confirm
Idempotency-Key: <a uuid>
```

Confirm works only in `awaiting_confirmation`, otherwise
`409 PAYOUT_BATCH_NOT_CONFIRMABLE`. Valid lines go to creation; resubmit fixed
`invalid` lines as a new batch with a new `externalReferenceId`.

With approvals required, the first confirm answers `202` with one approval for
the whole run:

```json
{ "status": "pending_approval", "approvalId": "cmf3k2xf90008q8b7q4r6s8tu", "requiredApprovals": 2, "expiresAt": "…" }
```

At quorum the run is released within a minute. A repeat confirm returns the same
approval; after a rejection it is `409 PAYOUT_BATCH_AWAITING_APPROVAL`, and
cancel still works ([Approvals](/status/#approvals)).

```http
POST /payments/organizations/{orgId}/payouts/batches/{batchId}/cancel
Idempotency-Key: <a uuid>
```

Cancel works in `received`, `validating` or `awaiting_confirmation`, so a
canceled batch never produces a payout; later it is
`409 PAYOUT_BATCH_NOT_CANCELABLE`. A created payout can be canceled with
`POST …/payouts/{payoutId}/cancel` only while it waits on your own funding.

## 5. Join the run to your ledger

```http
GET /payments/organizations/{orgId}/payouts/batches/{batchId}/items?status=created
```

Each created payout behaves like one sent alone: it appears in `GET /orders`,
emits `payout.*` events, and can still fail or be returned
([Track status & failures](/status/)). Reconcile through the
[event feed](/reconciliation/).
