PayintoDocs

Bulk transfers

Pay many recipients with one submission, once or on a schedule.

Bulk transfers let you build a list of recipients, check them, and submit them together. This module is called bulk_transfers.

Test with an sk_test_ key first. Sandbox bulk transfers run on your sandbox balance and pay simulated recipients, so no real money moves.

Send a bulk transfer

Create a draft

account_id is the account to debit, and a merchant_reference lets you retry safely. To pay everyone the same amount, also send use_same_amount: true and same_amount.

curl https://api.payinto.co/api/v1/business/bulk-transfers \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "account_id": "01964316-57cb-7284-8651-c53d3fcee694",
    "name": "October vendor payouts",
    "merchant_reference": "payouts-2026-10"
  }'

Add and resolve recipients

Add each recipient, then resolve them to confirm the account name. A bank code and account number can appear only once. Check resolved_account_name before you submit.

curl https://api.payinto.co/api/v1/business/bulk-transfers/$BULK_ID/items \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "bank_code": "058",
    "bank_name": "Guaranty Trust Bank",
    "account_number": "0123456789",
    "amount": 250000,
    "narration": "October invoice"
  }'
curl --request POST https://api.payinto.co/api/v1/business/bulk-transfers/items/$ITEM_ID/resolve \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Accept: application/json"

Submit

Your available balance must cover the requested amount and expected fees.

curl --request POST https://api.payinto.co/api/v1/business/bulk-transfers/$BULK_ID/submit \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Accept: application/json"

The status becomes queued. If your business uses an approval workflow, it becomes awaiting_approval until someone approves it in the dashboard.

Submitting again is safe. You get the current state and the X-Idempotency-Replay: true header, and nobody is paid twice.

Track the result

Subscribe to the bulk_transfer.finished webhook, which is sent when every recipient has reached a final status. You can also call GET /bulk-transfers/{id} for each recipient's status and attempts, or GET /bulk-transfers/{id}/export for a CSV.

If some recipients fail, the status is partially_successful. Fix the cause, then call POST /bulk-transfers/{id}/retry to try only the failed recipients again.

Import recipients from a file

To add many recipients at once, upload a CSV or XLSX file of up to 5 MB. Download the columns from GET /bulk-transfers/template.

Import amounts are in naira

Unlike the rest of the API, amounts in an import file are in naira, not kobo. 2500 in a file means ₦2,500.00.

Check a file first with POST /bulk-transfers/{id}/import/preview, which doesn't change the draft. Then send the same file to the import endpoint:

curl https://api.payinto.co/api/v1/business/bulk-transfers/$BULK_ID/import \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Accept: application/json" \
  --form "[email protected]"

Valid rows are added, and each invalid row is listed under errors. Imported recipients still need to be resolved.

Reuse a bulk transfer

POST /bulk-transfers/{id}/duplicate creates a new draft with the same recipients. Each recipient must be resolved again.

Statuses

StatusMeaning
draftYou can still change it.
awaiting_approvalWaiting for approval in the dashboard.
queued, processingPayments are in progress.
completedEvery recipient was paid.
partially_successfulSome recipients were paid, and some failed.
failedNo recipient was paid.

Only draft and ready bulk transfers can be edited. Recipients can only change while the status is draft, and only drafts can be deleted.

Schedule a bulk transfer

Create a schedule from a bulk transfer whose recipients are all resolved. The first run must be at least 10 minutes away. Keep that bulk transfer, because live runs use it as a template.

curl https://api.payinto.co/api/v1/business/scheduled-bulk-transfers \
  --header "Authorization: Bearer $PAYINTO_SECRET_KEY" \
  --header "Content-Type: application/json" \
  --header "Accept: application/json" \
  --data '{
    "source_account_id": "01964316-57cb-7284-8651-c53d3fcee694",
    "bulk_transfer_id": "0196431d-7a10-7e22-9c41-6b2d8f30c955",
    "merchant_reference": "monthly-vendors",
    "schedule": {
      "frequency": "monthly",
      "interval": 1,
      "starts_at": "2026-12-01T09:00:00+01:00",
      "timezone": "Africa/Lagos",
      "ends_type": "occurrences",
      "max_occurrences": 12
    }
  }'
FieldValues
frequencyonce, daily, weekly, monthly, yearly, custom
ends_typenever, date, occurrences
weekdaysFor weekly schedules, days from 0 (Sunday) to 6 (Saturday)
month_dayFor monthly schedules, 1 to 31
custom_unitdays, weeks, months, years

If your business requires approval, the response is 202 and the status is pending_approval.

Manage a schedule

Use /pause, /resume, and /cancel to control a schedule. Schedules that are active or paused can be edited with PATCH.

Check each run

Each run creates a new bulk transfer and sends its own bulk_transfer.finished event. GET /scheduled-bulk-transfers/{id}/executions lists the runs, and each one has the bulk_transfer_id it created. You can also call GET /bulk-transfers?scheduled_payment_id={id}.

Good to know

  • Limits apply to the whole submission. The total is checked against your account's daily debit limit, and each transfer is checked again when it's processed.
  • Requests act as the business owner. Payments and audit records created with your API key are attributed to the owner, so your business needs an active owner.
  • Sandbox fees are simulated. Live fees can differ.

On this page