Bulk Payout

Send payments to multiple recipients in a single batch using Atlas bulk payouts.

Bulk payouts allow you to transfer funds from your Atlas business account to multiple recipients in a single API call. The batch is processed asynchronously, making it well-suited for payroll runs, mass vendor disbursements, and any scenario where you need to pay many recipients at once.

Initiating a bulk payout

Initiating a bulk payout is a two-step process: first validate all recipient accounts, then send the batch to Atlas. The request body takes:

  • source_reference (required): A unique string that identifies the batch.
  • currency (required): The currency of every payout in the batch. Use NGN.
  • name (optional): A label for the batch, such as March 2026 vendor payments.
  • payouts (required): An array with one object per recipient. Each object takes the same fields as a single payout: account_number, bank_code, bank_name, account_name, amount, an optional narration, and its own source_reference, which must be unique within the batch.

A batch can contain any number of payouts, from one upwards.

Validating recipient accounts

Before sending the batch, validate all recipient accounts using the Run Bulk Account Name Enquiry endpoint. Pass the currency and an array of account numbers and bank codes:

curl -X POST "https://atlas.tryduplo.com/api/v1/banking/bulk-name-enquiry" \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "NGN",
    "accounts": [
      { "account_number": "0123456789", "bank_code": "090267" },
      { "account_number": "2233445566", "bank_code": "000015" },
      { "account_number": "9876543210", "bank_code": "000016" }
    ]
  }'

A successful response returns the account name registered with the bank for each account:

JSON
{
  "requestId": "d3a1f200-84bc-4c21-a6f2-cc7e90d14b11",
  "requestTimestamp": "2026-03-21 10:00:00.000",
  "message": "Bulk account name enquiry completed.",
  "statusCode": 200,
  "data": [
    {
      "accountNumber": "0123456789",
      "accountName": "John Doe",
      "bankCode": "090267",
      "sessionId": "100004260321100000000000000001"
    },
    {
      "accountNumber": "2233445566",
      "accountName": "Adaeze Okafor",
      "bankCode": "000015",
      "sessionId": "100004260321100000000000000002"
    },
    {
      "accountNumber": "9876543210",
      "accountName": "Fatima Bello",
      "bankCode": "000016",
      "sessionId": "100004260321100000000000000003"
    }
  ]
}

Check each returned accountName against the recipient you intend to pay. If any account can't be resolved, data instead holds the raw result from the bank network, with an error on each account that failed. Fix or remove those accounts before you send the batch.

Atlas runs the same bulk name enquiry again when you send the batch. If any account fails, it rejects the whole batch, and it pays each recipient under the name their bank returns rather than the account_name in your request.

Sending the batch

With recipient accounts validated, use the Initiate Bulk Bank Payout endpoint to queue the batch.

Ensure sufficient balance

Your Atlas wallet must cover the total of every payout in the batch plus their fees. Atlas checks this before creating the batch and rejects the whole batch if the balance is short.

curl -X POST "https://atlas.tryduplo.com/api/v1/payout/bank-transfer/bulk" \
  -H "Authorization: Bearer your_api_key" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "source_reference": "BULK_PAYOUT_001",
    "name": "March 2026 vendor payments",
    "currency": "NGN",
    "payouts": [
      {
        "account_number": "0123456789",
        "bank_code": "090267",
        "bank_name": "Kuda Microfinance Bank",
        "account_name": "John Doe",
        "amount": 8000,
        "narration": "Vendor payment - March 2026",
        "source_reference": "BULK_PAYOUT_001_1"
      },
      {
        "account_number": "2233445566",
        "bank_code": "000015",
        "bank_name": "Zenith Bank Plc",
        "account_name": "Adaeze Okafor",
        "amount": 9500,
        "narration": "Vendor payment - March 2026",
        "source_reference": "BULK_PAYOUT_001_2"
      },
      {
        "account_number": "9876543210",
        "bank_code": "000016",
        "bank_name": "FIRST BANK OF NIGERIA PLC",
        "account_name": "Fatima Bello",
        "amount": 6500,
        "narration": "Vendor payment - March 2026",
        "source_reference": "BULK_PAYOUT_001_3"
      }
    ]
  }'

Atlas accepts the batch and processes it asynchronously, so a successful request returns a 202 status code. The response includes the batchReference you can use to track the batch, and the total amount and fees debited from your wallet:

JSON
{
  "requestId": "7f3a1b22-c4d8-4e91-b2a0-5e6f7c8d9e0f",
  "requestTimestamp": "2026-03-21 12:00:00.000",
  "message": "Bulk payout accepted and queued for processing",
  "statusCode": 202,
  "data": {
    "batchId": "019d1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
    "totalAmount": {
      "value": 24000,
      "currency": "NGN",
      "formatted": "NGN24000"
    },
    "totalFees": {
      "value": 150,
      "currency": "NGN",
      "formatted": "NGN150"
    },
    "totalPayouts": 3,
    "batchReference": "bat_pay_8k2j5x1m9q4r",
    "sourceReference": "BULK_PAYOUT_001"
  }
}

Sending the same batch again with the same source_reference is safe: Atlas returns the existing batch instead of creating a new one. Sending a different batch with a source_reference you have already used is rejected.

Unconfirmed batches

If data includes "dispatchState": "unconfirmed", Atlas couldn't confirm whether the batch reached the payment network. Don't send it again with a new source_reference, because that could pay every recipient twice. Check the batch status instead, and wait for the payout webhooks.

Checking bulk payout status

Because bulk payouts are processed asynchronously, you can poll for the batch status using either the batchReference Atlas returned or your source_reference.

By batch reference

Use the Get Bulk Payout Status endpoint:

curl -X GET "https://atlas.tryduplo.com/api/v1/payout/bulk/bat_pay_8k2j5x1m9q4r/status" \
  -H "Authorization: Bearer your_api_key" \
  -H "Accept: application/json"

By source reference

If you'd prefer to look up the batch using your own source_reference, use the Get Bulk Payout Status By Source Reference endpoint:

curl -X GET "https://atlas.tryduplo.com/api/v1/payout/bulk-by-source-reference/BULK_PAYOUT_001/status" \
  -H "Authorization: Bearer your_api_key" \
  -H "Accept: application/json"

Both status endpoints return the same response shape: the batch status, its totals, and every payout in the batch with its own status. A COMPLETED batch means every payout has reached a final state, not that every payout succeeded, so check each payout's status:

JSON
{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "requestTimestamp": "2026-03-21 12:05:00.000",
  "message": "Bulk payout status fetched successfully.",
  "statusCode": 200,
  "data": {
    "batchId": "019d1a2b-3c4d-7e5f-8a9b-0c1d2e3f4a5b",
    "batchReference": "bat_pay_8k2j5x1m9q4r",
    "sourceReference": "BULK_PAYOUT_001",
    "name": "March 2026 vendor payments",
    "status": "COMPLETED",
    "totalAmount": { "value": 24000, "currency": "NGN", "formatted": "NGN24000" },
    "totalFees": { "value": 150, "currency": "NGN", "formatted": "NGN150" },
    "totalPayouts": 3,
    "payouts": [
      {
        "id": "019d1a2b-4d5e-7f60-8b9c-1d2e3f4a5b6c",
        "recipientAccountName": "John Doe",
        "recipientAccountNumber": "0123456789",
        "recipientBankName": "Kuda Microfinance Bank",
        "sourceReference": "BULK_PAYOUT_001_1",
        "paymentChannel": "Bank Transfer",
        "sessionId": "100004260321120000000000000101",
        "parentTransactionReference": null,
        "reference": "TRN_XYZABC000001",
        "amount": { "value": 8000, "currency": "NGN", "formatted": "NGN8000" },
        "balance": { "value": 92000, "currency": "NGN", "formatted": "NGN92000" },
        "status": "Successful",
        "narration": "Vendor payment - March 2026",
        "createdAt": "2026-03-21 12:00:01"
      },
      {
        "id": "019d1a2b-5e6f-7071-8c9d-2e3f4a5b6c7d",
        "recipientAccountName": "Adaeze Okafor",
        "recipientAccountNumber": "2233445566",
        "recipientBankName": "Zenith Bank Plc",
        "sourceReference": "BULK_PAYOUT_001_2",
        "paymentChannel": "Bank Transfer",
        "sessionId": "100004260321120000000000000102",
        "parentTransactionReference": null,
        "reference": "TRN_XYZABC000002",
        "amount": { "value": 9500, "currency": "NGN", "formatted": "NGN9500" },
        "balance": { "value": 82450, "currency": "NGN", "formatted": "NGN82450" },
        "status": "Successful",
        "narration": "Vendor payment - March 2026",
        "createdAt": "2026-03-21 12:00:01"
      },
      {
        "id": "019d1a2b-6f70-7182-8dae-3f4a5b6c7d8e",
        "recipientAccountName": "Fatima Bello",
        "recipientAccountNumber": "9876543210",
        "recipientBankName": "FIRST BANK OF NIGERIA PLC",
        "sourceReference": "BULK_PAYOUT_001_3",
        "paymentChannel": "Bank Transfer",
        "sessionId": null,
        "parentTransactionReference": null,
        "reference": "TRN_XYZABC000003",
        "amount": { "value": 6500, "currency": "NGN", "formatted": "NGN6500" },
        "balance": { "value": 75900, "currency": "NGN", "formatted": "NGN75900" },
        "status": "Failed",
        "narration": "Vendor payment - March 2026",
        "createdAt": "2026-03-21 12:00:01"
      }
    ]
  }
}

The batch status is PENDING while any payout is still pending, COMPLETED once they have all reached a final state, and FAILED if Atlas rejected the whole batch before sending any of it. See payout states for details.

Atlas also sends a webhook for each payout in the batch as it is processed. You will receive an OUT_FLOW_PENDING_EVENT when a payout is initiated, then either an OUT_FLOW_SUCCESS_EVENT when it completes or an OUT_FLOW_FAILED_EVENT if it fails. See the webhook events reference for the full payload shapes.

How is this guide?

Last updated on

On this page