Skip to content

payouts.batches

A payout batch is one disbursement run, frozen. Building it moves eligible ledger rows onto the batch so nothing else can claim them; exporting renders the file; confirming records what the rail actually did.

const batch = await boomin.payouts.batches.create({
periodStart: "2026-08-01",
periodEnd: "2026-09-01",
});
const accepted = await boomin.payouts.batches.export(batch.id);
await boomin.operations.wait(accepted.operation, { timeout: 120000 });
const exported = await boomin.payouts.batches.retrieve(batch.id);
console.log(exported.downloadUrl);
MethodRouteScope
create(params, options)POST /payouts/batchespayouts:write
list(params, options)GET /payouts/batchespayouts:read
retrieve(id, options)GET /payouts/batches/{id}payouts:read
export(id, params, options)POST /payouts/batches/{id}/exportpayouts:write
confirm(id, params, options)POST /payouts/batches/{id}/confirmpayouts:write
cancel(id, params, options)POST /payouts/batches/{id}/cancelpayouts:write

Ids are prefixed pob_.

{
"id": "pob_...",
"object": "payout_batch",
"rail": "csv_batch",
"status": "exported",
"currency": "usd",
"periodStart": "2026-08-01",
"periodEnd": "2026-09-01",
"itemCount": 12,
"totalAmountCents": 48500,
"exportFileKey": "payouts/…/pob_….csv",
"exportFormat": "paypal_payouts_csv",
"externalBatchRef": null,
"exportedAt": "2026-08-03T06:20:11.104Z",
"confirmedAt": null,
"completedAt": null,
"error": null,
"createdAt": "2026-08-03T06:19:44.201Z",
"items": [ ],
"downloadUrl": "https://…"
}

(Raw HTTP responses spell these period_start, total_amount_cents, and so on; the SDK camelCases every response key.)

items accompanies create and retrieve. downloadUrl appears on retrieve only — see where downloadUrl lives.

Note the batch total is totalAmountCents (wire total_amount_cents), not *Minor: the batch is a single-currency object and this is a pre-existing physical field name. Rule economics use perUnitMinor / bonusMinor.

draft exporting exported submitted reconciling completed partially_paid failed canceled

{
"id": "2d7c12ab-…",
"payoutId": "",
"entityId": "",
"userId": null,
"recipientHandle": "[email protected]",
"amountCents": 2500,
"currency": "usd",
"status": "pending",
"externalItemRef": null,
"failureReason": null,
"paidAt": null
}

Item id is a bare uuid, not a prefixed id — it is what confirm names in results[].item. Item statuses are pending processing paid failed returned canceled.

Synchronous. Freezes the eligible rows onto a new batch and resolves the batch plus its items and skipped. Answers 201.

const batch = await boomin.payouts.batches.create({
rail: "csv_batch", // optional — omit to use the brand's default rail
periodStart: "2026-08-01", // optional
periodEnd: "2026-09-01", // optional
});

Omit both period fields to sweep every eligible row regardless of period.

It is synchronous because the build is a single database transaction whatever the item count — there is no unbounded work to make durable, and a 202 would hand back an operation id for something already finished.

A ledger row joins a batch when its status is pending or awaiting_account, and it is not bridged to another brand’s wallet. Bridged rows settle on the wallet rail instead — never both, never twice.

A stripe_connect batch takes pending rows only.

const batch = await boomin.payouts.batches.create({ periodStart, periodEnd });
console.log(batch.itemCount, batch.skipped); // 12 3

skipped is the number of otherwise-eligible rows dropped for want of a recipient handle — no email on csv_batch, no onboarded Stripe account on stripe_connect. Those rows stay eligible and will join a later batch once the handle exists.

If every eligible row is skipped, the build answers payout_batch_empty (409) carrying the same count.

import { PayoutRailRequiredError } from "@boomin/sdk";
try {
await boomin.payouts.batches.create({ periodStart, periodEnd });
} catch (err) {
if (err instanceof PayoutRailRequiredError) {
// configure one — nothing is auto-provisioned
} else throw err;
}

Every “not configured” path — no rail of the named kind, no default, a default nothing can batch, a disabled rail — collapses into that one code, because from your side they are one problem with one fix. See why nothing is auto-created.

202 + an operation. Writes the rendered file to storage.

const accepted = await boomin.payouts.batches.export("pob_...");
// { batch: "pob_...", status: "exporting", operation: "op_..." }
const operation = await boomin.operations.wait(accepted.operation, { timeout: 120000 });
if (operation.status !== "succeeded") throw new Error(`export ${operation.status}`);

batch and operation are id strings, never embedded objects — the same 202 contract payouts.exportCsv answers. One export contract, not two.

Repeating the call replays the same operation, and even a re-run writes the same storage key: one batch can never produce two artifacts.

Not in the 202. It is minted on read:

const batch = await boomin.payouts.batches.retrieve(accepted.batch);
console.log(batch.downloadUrl);

The URL is presigned and short-lived. Returned once from the mutation it would already be expiring by the time an operator opened it, and could not be re-obtained without re-exporting. On retrieve it is regenerated every time.

downloadUrl is null on a batch that has an exportFileKey when presigning credentials are unavailable — the file exists but was not delivered. Treat that as a failure rather than writing a zero-byte file.

202 + an operation. Records the outcome of the disbursement you performed.

const accepted = await boomin.payouts.batches.confirm("pob_...", {
externalBatchRef: "PAYPAL-2026-08",
});
await boomin.operations.wait(accepted.operation);

With no results, every item settles as paid. To report per-item outcomes:

const batch = await boomin.payouts.batches.retrieve("pob_...");
await boomin.payouts.batches.confirm(batch.id, {
externalBatchRef: "PAYPAL-2026-08",
results: [
{ item: batch.items[0].id, status: "paid" },
{ item: batch.items[1].id, status: "failed", reason: "recipient email bounced" },
{ item: batch.items[2].id, status: "returned", reason: "account closed" },
],
});
ParamMeaning
externalBatchRefYour rail-side batch id, ≤ 200 chars. Also the retry key — see below.
results[].itemA batch item id (bare uuid) from batch.items
results[].statuspaid | failed | returned
results[].reasonOptional, ≤ 500 chars

Up to 1000 results. Naming an item that is not in this batch is a typed 400 with param: "results" — it never partially applies.

Repeating a confirm with the same externalBatchRef replays one operation, so an operator’s retry after a timeout cannot settle the run twice.

Synchronous. Unfreezes a batch that has not settled and returns its rows to the eligible pool.

const canceled = await boomin.payouts.batches.cancel("pob_...");
console.log(canceled.status); // "canceled"
const page = await boomin.payouts.batches.list({ limit: 20 });
const batch = await boomin.payouts.batches.retrieve("pob_...");
console.log(batch.items, batch.downloadUrl);

list accepts limit (1–100, default 20) and startingAfter. Batches are few and already ordered, so paging is applied in memory — but it is applied.

retrieve returns the bare batch plus items and downloadUrl alongside.

CodeHTTPWhen
payout_rail_required409No active rail of the requested kind, or no usable default. → PayoutRailRequiredError
payout_batch_empty409No settle-able row for this rail and period. → PayoutBatchEmptyError
payout_batch_conflict409A concurrent build raced this one. → PayoutBatchStateError
payout_batch_not_exportable409Wrong status for export. → PayoutBatchStateError
payout_batch_not_confirmable409Wrong status for confirm. → PayoutBatchStateError
payout_batch_not_cancelable409Wrong status for cancel. → PayoutBatchStateError
invalid_request400results names an item not in this batch.
payout_batch_not_found404Unknown, malformed, or another tenant’s batch id.

On any PayoutBatchStateError, read the batch and look at status — the state machine refused the verb, and the current status says why.

Terminal window
npx @boomin/cli payout batches create --period-start 2026-08-01 --period-end 2026-09-01
npx @boomin/cli payout batches list
npx @boomin/cli payout batches show pob_...
npx @boomin/cli payout batches export pob_... --out payouts.csv
npx @boomin/cli payout batches confirm pob_... --external-batch-ref PAYPAL-2026-08
npx @boomin/cli payout batches cancel pob_...

export and confirm poll their operation to a terminal status by default; --no-wait returns the 202. Full flag table: CLI reference.