---
title: Payout batches
description: Send up to 100 payouts in one request.
---

A batch sends many payouts in one call. Each item is an ordinary payout. It is accepted or refused on its own, so one refusal does not stop the others.

## Send a batch

```http
POST /v1/payout-batches
Idempotency-Key: payroll-2026-10
```

```json
{
  "reference": "payroll-2026-10",
  "items": [
    {
      "reference": "payroll-2026-10-001",
      "amount": "450.00",
      "currency": "GHS",
      "provider": "mtn",
      "destination": { "type": "msisdn", "value": "233244123456" }
    },
    {
      "reference": "payroll-2026-10-002",
      "amount": "380.00",
      "currency": "GHS",
      "provider": "telecel",
      "destination": { "type": "msisdn", "value": "233201234567" }
    }
  ]
}
```

- `reference` is yours, and unique for each batch.
- `items` holds 1 to 100 payouts. Each has the same fields as `POST /v1/payouts`.
- Each item's `reference` must be unique within the batch, and is the payout's own reference.

## What comes back

If the batch itself is malformed, the whole batch is refused with `400 invalid_input` and nothing is sent. That covers a missing `reference`, no items, more than 100 items, an invalid item, or a reference used twice in the batch. `message` names the item.

Otherwise you get `200`, with one entry per item in the order you sent them:

```json
{
  "batchId": "b2f1c7e4-5a1d-4c3e-9f0a-7d6b2e8c1a90",
  "reference": "payroll-2026-10",
  "items": [
    {
      "reference": "payroll-2026-10-001",
      "status": 202,
      "payout": {
        "reference": "payroll-2026-10-001",
        "amount": "450.00",
        "currency": "GHS",
        "status": "accepted",
        "provider": "mtn",
        "createdAt": "2026-10-05T09:00:00Z"
      }
    },
    {
      "reference": "payroll-2026-10-002",
      "status": 403,
      "error": {
        "code": "daily_limit_exceeded",
        "message": "daily limit exceeded"
      }
    }
  ]
}
```

The values above only show the shape.

- `status` is `202` when the payout was accepted. It then carries the `payout`.
- Any other `status` is the one a single payout would have been refused with. `error` has the same code, such as `daily_limit_exceeded`.

A `200` does not mean every payout went out. Check each item.

## Limits

Your limits apply to each item as if it were sent alone. A batch cannot move more than the same payouts sent one by one. Check `GET /v1/limits` before a large batch.

## Retrying

After a timeout or a `5xx`, retry with the same `Idempotency-Key` and the same body. Accepted items are not sent again.

## Following the payouts

There is no batch read. Read each payout with `GET /v1/payouts/{reference}`. Webhooks arrive for each payout (`payout.accepted`, `payout.status_changed`), not for the batch. See [Webhooks](/guides/webhooks).

## Permissions

| To              | The key needs         |
| --------------- | --------------------- |
| Send a batch    | `disbursements.write` |
| Read a payout   | `disbursements.read`  |

## Errors

As well as the [common errors](/guides/errors):

| Status | Code                     | What to do                                                      |
| ------ | ------------------------ | --------------------------------------------------------------- |
| 400    | `invalid_input`          | The batch is malformed and nothing was sent. Fix the item `message` names and send again. |
| 409    | `idempotency_key_reused` | This key was used with a different body. Send the original body, or use a new key for a new batch. |
| 409    | `request_in_flight`      | The first request with this key is still running. Wait, then retry the same request. |
