---
title: "Send N transfers under one confirmation"
method: POST
path: "/v1/transfers/batch"
tags: ["Partner API"]
---

# Send N transfers under one confirmation

`POST /v1/transfers/batch`

## Request

- Base URL: `https://sandbox.onlink.africa`
- URL: `https://sandbox.onlink.africa/v1/transfers/batch`
- Auth: API key in header `X-OnLink-Key`

A loop over POST /v1/transfers behind a single confirmation code (spec §2.3). Each item is validated exactly as a single transfer would be; a failed item is reported with its refusal reason and the rest proceed. Nothing reaches the bank until the one code is verified via POST /v1/confirmations/:id, unless OnLink has enabled confirmation waiving for your account and, in production, the API key has an IP allowlist, in which case the batch is returned already confirmed and is executed immediately.

## Request body

- PartnerCreateBatchDto
  - `idempotencyKey` string, required: A key unique to your account, for the WHOLE batch. A retried create with the same key returns the original batch rather than submitting it twice. Each item ALSO carries its own idempotencyKey — that is what dedupes the individual payments.
  - `items` PartnerBatchItemDto[], required: The transfers to submit, each shaped exactly like POST /v1/transfers. A batch is a loop over the single-transfer behaviour behind one confirmation — an item that fails validation is reported failed and the rest proceed.
    - `rail` 'mpesa' | 'pesalink' | 'rtgs' | 'eft' | 'swift' | 'internal', required: The rail to send on.
    - `destination` TransferDestinationDto, required
      - `accountNumber` string, required: Beneficiary account number. For mpesa, a Kenyan mobile number (any recognised local or international format — normalised server-side); for the other rails, the beneficiary account number.
      - `accountName` string, required: Beneficiary name as you believe it to be — verified server-side against the rail before the transfer is created.
      - `bankCode` string: Beneficiary bank code. Required for pesalink, rtgs, eft. Not allowed for mpesa, swift, internal.
      - `bankName` string: Beneficiary bank name. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.
      - `branchCode` string: Beneficiary bank branch code. Optional for rtgs, eft. Not allowed for mpesa, pesalink, swift, internal.
      - `swift` SwiftDestinationDto
        - `bic` string, required: Beneficiary bank BIC/SWIFT code (8 or 11 characters).
        - `bankCity` string, required: Beneficiary bank city.
        - `bankCountry` string, required: Beneficiary bank country, spelled out in FULL — never an ISO code.
        - `chargeType` 'OUR' | 'BEN' | 'SHA': Who bears the SWIFT charge. Defaults to OUR (you pay).
        - `accountCurrency` string: ISO currency of the beneficiary account. Defaults to the send currency. A USD transfer must pay a USD account.
    - `amount` TransferAmountDto, required
      - `currency` 'KES' | 'USD', required: Send currency, and the account it is paid from: KES from your KES account, USD from your USD account. Nothing is converted. USD is accepted on rtgs, eft and swift only (to a USD beneficiary account); any other rail refuses it with TRANSFER_RAIL_CURRENCY_UNSUPPORTED. USD is for Collections accounts only: a USDT Rails account is refused TRANSFER_CURRENCY_NOT_ENABLED on any rail.
      - `value` string, required: Decimal string, major units of `currency`, up to 2 decimal places, GREATER than zero. Never a JSON number.
    - `partnerReference` string: Your own reference for this transfer, echoed back on every read.
    - `idempotencyKey` string, required: A key unique to your account. A retried create with the same key and the same request body returns the original transfer rather than creating a second one; the same key with a DIFFERENT request body is refused.
    - `paymentPurposeId` string: Payment purpose code. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.
    - `senderAddress` string: Your registered address. Required for rtgs, eft, swift. Not allowed for mpesa, pesalink, internal.
    - `remark` string: Optional free-text remark. Allowed on every rail.

## Response `202`

The batch was created. Normally a confirmation code is sent and the response carries a `confirmation` object. When OnLink has enabled confirmation waiving for your account and, in production, the API key has an IP allowlist, the batch is created already `confirmed`, executes immediately, and the response carries no `confirmation` object.

- PartnerBatchResponseDto
  - `id` string, required: The batch id.
  - `status` 'awaiting_confirmation' | 'confirmed' | 'processing' | 'completed' | 'failed' | 'expired', required: The batch status.
  - `itemCount` number, required: Total items submitted, valid or not.
  - `acceptedCount` number, required: Items that reached a successful terminal state.
  - `failedCount` number, required: Items refused at submit or failed by the executor.
  - `items` PartnerBatchItemResultDto[], required: Every item's outcome, in the order submitted.
    - `transferId` string: The transfer id, present when the item was accepted. Read it with GET /v1/transfers/:id for its full detail.
    - `idempotencyKey` string, required: Your own reference for this item — echoes the item's own idempotencyKey.
    - `accepted` boolean, required: Whether this item was accepted into the batch.
    - `failureCode` string: Present only when accepted is false — one of the single-transfer refusal codes (TRANSFER_NAME_MISMATCH, TRANSFER_EXCEEDS_DAILY_CAP, …).
  - `createdAt` string, date-time, required: ISO-8601 instant the batch was created.
  - `confirmedAt` string, date-time: ISO-8601 instant the batch was confirmed: when its confirmation code was verified, or, when confirmation is waived for your account, when the batch was created already confirmed.
  - `confirmation` ConfirmationDto
    - `id` string, required: The confirmation id. POST the code your confirmation contact receives (or your webhook, as `confirmation.code_issued`, when `channel` is `webhook`) back to `/v1/confirmations/:id` to authorise the transfer.
    - `channel` 'email' | 'sms' | 'webhook', required: How the confirmation code was delivered. `webhook` means it was pushed to your webhook endpoint as `confirmation.code_issued`.
    - `deliveredTo` string, required: Masked delivery address or number. Never the raw contact.
    - `expiresAt` string, date-time, required: ISO-8601 instant the confirmation code expires.
    - `resendAvailableAt` string, date-time, required: ISO-8601 instant a resend becomes available.
    - `webhookDelivery` ConfirmationWebhookDeliveryDto
      - `status` 'pending' | 'delivered' | 'failed', required: `pending` while we are still trying, `delivered` once your endpoint acknowledged the push, `failed` once every attempt has failed.
      - `attempts` integer, required: How many push attempts have been made so far.
      - `lastAttemptAt` string, date-time, nullable, required: ISO-8601 instant of the most recent push attempt, or null before the first.
    - `waiverNotApplied` 'key_without_ip_allowlist': Present only when OnLink has enabled confirmation waiving for your account but this request still needs a confirmation code, because the API key that made it has no IP allowlist. Ask OnLink to add an IP allowlist to that API key; payouts made with it are then created already confirmed. Present on the first response only: a repeat of the same request (an idempotent replay) returns the stored payout without it. Absent in every other case.

## Other responses

- `422`: BATCH_TOO_LARGE, BATCH_CURRENCY_MIXED (the items send more than one currency), TRANSFER_CURRENCY_NOT_ENABLED (a USD batch from a USDT Rails account), or BATCH_NO_VALID_ITEMS.
- `503`: PARTNER_BATCH_TRANSFERS_DISABLED, MONEY_MOVEMENT_PAUSED, CONFIRMATION_CONTACT_MISSING, or CONFIRMATION_DELIVERY_UNAVAILABLE. The two CONFIRMATION_* refusals mean no confirmation code can be delivered; when the cause is a waived account whose API key has no IP allowlist, the body also carries `details.waiverNotApplied: "key_without_ip_allowlist"`. Nothing was created.

---

[API](https://skmtc.dev/onlink/apis/onlink-partner-api.md) - [All operations](https://skmtc.dev/onlink/apis/onlink-partner-api/llms.txt) - [OpenAPI document](https://skmtc.dev/onlink/apis/onlink-partner-api/revisions/1d1c6a3671f2?raw)
