Skip to content

Batch payments

A payment batch pays many recipients at once: payroll, or a run of supplier invoices. Every recipient is paid by bank transfer in the same currency, and one approval covers the whole batch.

Read Paying out first. A batch uses the same contacts, quotes and approvals as a single payment.

  • Build a draft batch from saved contacts, from new recipients described inline, or from a CSV sheet.
  • Check every row, and price them all at once.
  • Submit the batch for one approval.
  • Follow each row’s payment, and retry the ones that failed.

Call createPaymentBatch with the payout_currency every recipient receives, a payout_reason code shared by every row, and the rows. A batch holds at most 500 rows.

Each row either names a saved, approved payment_contact_id, or describes a new recipient inline. amount is in the payout currency: what the recipient receives. Set the batch’s amount_side to deducting to enter what each row costs instead. reference is what the recipient’s statement shows, at most 35 characters; without it, the row uses the batch’s statement_description.

Terminal window
curl -s "$ELE_API_BASE/api/v1/organizations/$ORGANIZATION_ID/payment-batches" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "October payroll",
"payout_currency": "SGD",
"source_currency": "USD",
"payout_reason": "PYR005",
"statement_description": "October salary",
"rows": [
{"payment_contact_id": "'"$CONTACT_ID"'", "amount": "4200.00"},
{
"amount": "3900.00",
"reference": "Salary Oct",
"recipient": {
"account_holder_name": "Tan Mei Ling",
"account_type": "individual",
"country_alpha2": "SG",
"bank_details": {"bank_name": "DBS Bank Ltd", "account_number": "0987654321", "swift_code": "DBSSSGSG"}
}
}
]
}'

source_currency is the balance the batch is paid from. You can leave it out now and set it later with updatePaymentBatch, but the batch cannot be priced without it.

To run an earlier batch again, send rerun_of_batch_id to copy all its rows. To retry a completed batch, send retry_of_batch_id: it copies only the rows that failed or were cancelled, and those whose payment failed or was returned after it was sent. Use one or the other.

To start from a spreadsheet, get the sheet’s columns for the payout currency from getPaymentBatchTemplate. csv is a header row ready to save, and each column’s level says whether the currency’s banks need it.

Terminal window
curl -s "$ELE_API_BASE/api/v1/payment-batches/template?payout_currency=SGD" \
-H "Authorization: Bearer $ELE_TOKEN"

Then upload the filled sheet to a draft batch in three calls:

  1. createPaymentBatchUpload with the file’s filename, content_type and exact byte_size. A CSV may be at most 5 MB.

    Terminal window
    curl -s "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/upload" \
    -H "Authorization: Bearer $ELE_TOKEN" \
    -H "Idempotency-Key: $KEY" \
    -H 'Content-Type: application/json' \
    -d '{"filename": "payroll_oct.csv", "content_type": "text/csv", "byte_size": 18234}'
  2. PUT the raw file to upload_url before expires_at, sending exactly the headers from the response:

    Terminal window
    curl -s -X PUT "$UPLOAD_URL" \
    -H 'content-type: text/csv' \
    -H 'content-length: 18234' \
    --data-binary @payroll_oct.csv
  3. importPaymentBatchFile with the upload’s id as file_id:

    Terminal window
    curl -s "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/import" \
    -H "Authorization: Bearer $ELE_TOKEN" \
    -H "Idempotency-Key: $KEY" \
    -H 'Content-Type: application/json' \
    -d "{\"file_id\": \"$FILE_ID\"}"

Each line of the sheet becomes a row. A sheet Ele cannot read is refused with 422 invalid_file, and details.errors says why. A bad value on one line does not refuse the sheet: it shows up as an issue on that row. Importing again replaces the rows from the earlier sheet.

Each row of a draft has issues. An issue with level set to needs_fix stops the batch from being priced or submitted; a warning does not. field names what to fix, and message says it in plain words. The batch’s needs_fix_count must reach zero.

Change the draft with:

Terminal window
curl -s -X PATCH "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/rows/$ROW_ID" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"amount": "4000.00"}'

validatePaymentBatch checks every row again without pricing anything.

reviewPaymentBatch prices every row. It first saves each inline recipient as a contact that belongs to the batch, and registers the bank account with Ele’s payout partner.

Terminal window
curl -s -X POST "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/review" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY"

Each row gets a disposition. A ready row has a quote; any other value says why the row cannot be sent:

disposition Meaning
ready Priced, with a quote.
needs_fix The row has an issue to fix.
missing_amount The row has no amount.
currency_mismatch The contact’s bank account is in another currency.
destination_not_ready The bank details were refused, or are still being checked.
contact_not_approved The saved contact is not approved yet.
corridor_closed Ele cannot pay from the source currency to this recipient’s currency and country.
below_minimum The amount is below the minimum for this route.
quote_failed Pricing failed. error says why.

Each row’s destination is the bank account it pays, in the same shape a payment contact’s destination has. Show it to the person before they approve. It is null until review saves an inline recipient as a contact. destination_summary is deprecated; use destination.

The batch then shows total_deducting, the fees, and balance_shortfall if the source balance does not cover the total.

Each quote expires, as a single payment’s does. Any change to the draft, and any lapsed quote, means you must review again before you submit.

submitPaymentBatch turns every ready row into a payment awaiting approval and raises one approval request for the batch.

Terminal window
curl -s -X POST "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/submit" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY"

Ele refuses to submit unless every row is ready, every quote is live, and the source balance covers the total. The batch’s status becomes awaiting_approval, and approval_request_id names the request.

An owner or admin approves the request with approveRequest, which needs step-up. A person must complete it; see Step-up and approvals. Approving the batch also approves the new recipients it created.

If any row’s quote has lapsed by then, Ele refuses the approval with batch_quote_expired, and details.rows lists the row numbers. Re-price them with requotePaymentBatch, show the person the new total, and approve again:

Terminal window
curl -s -X POST "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/requote" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY"

Rows whose quote is still live keep it. The batch’s expired_row_count tells you when a requote is needed.

On approval, Ele sets the total aside from the source balance and starts sending the rows.

Poll getPaymentBatch until the batch is completed:

Terminal window
curl -s "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID" \
-H "Authorization: Bearer $ELE_TOKEN"

Each row’s result is written once Ele has handled it: sent, failed or cancelled. sent means the payment was handed over; the row’s payment_state says where that payment is now, using the same values as a payment’s status in Paying out. payment_id names the payment, which you can read with getPayment. When a row fails, error holds a code such as insufficient_funds, quote_drift, payment_failed or payment_reversed.

A row whose payment cannot go out yet is held, not failed: its payment_state is on_hold, and the batch’s held_count counts it. The batch stays executing until every held row is sent or resolved.

To pay the failed rows again once the batch is completed, create a new batch with retry_of_batch_id.

A batch’s status:

status Meaning What you do
draft Rows are being added, fixed and priced. Review, then submit.
awaiting_approval Submitted; waiting for a person. Ask an approver. Requote if quotes lapse.
executing Approved; rows are being sent. Poll.
completed Every row has a result. Check each row. Retry failed rows if needed.
cancelled Cancelled or rejected before it was sent. Nothing.

Building, reviewing and submitting a batch need neither step-up nor approval. Approving it (approveRequest) needs step-up, by an owner or admin. One approval covers every row: the rows have no approval requests of their own.

A draft belongs to the person who started it. Only they can see, change, review, submit or delete it. Once submitted, the organization’s approvers can see it too, and can requote or cancel it.

  • A draft: delete it with deletePaymentBatch.
  • Awaiting approval: cancel it with cancelPaymentBatch, or reject its approval request. Every row’s payment is cancelled with it.
  • Executing: an approved batch cannot be cancelled here.
Terminal window
curl -s -X POST "$ELE_API_BASE/api/v1/payment-batches/$BATCH_ID/cancel" \
-H "Authorization: Bearer $ELE_TOKEN" \
-H "Idempotency-Key: $KEY"
Status type When What to do
404 not_found The batch is another person’s draft, or does not exist. Check the ID and who started it.
409 invalid_status The call does not fit the batch’s status, such as editing a submitted batch. Read the batch and decide.
409 batch_modified The batch changed while it was being priced. Read it and try again.
409 not_awaiting_approval Cancelling a batch that is not awaiting approval. Delete a draft instead.
422 source_currency_required Reviewing or submitting without a source balance. Set source_currency.
422 rows_not_ready Submitting while some rows are not ready, or their quotes lapsed. details.rows lists them. Fix the rows and review again.
422 no_rows Submitting a batch with no rows. Add rows.
422 too_many_rows More than 500 rows. Split the batch.
422 insufficient_funds The source balance does not cover the batch, at submit or at approval. Add money, or remove rows.
422 batch_quote_expired Approving with lapsed quotes. details.rows lists them. Requote, then approve again.
422 invalid_file The CSV cannot be read. details.errors says why. Fix the sheet.
422 file_not_uploaded Importing before the PUT finished. Upload the file first.
422 payout_reason_not_found The payout_reason code is unknown. Use a code from getPaymentsConfig.

For other types, see Errors.