BTC$68,420▲ 0.82% / ETH$3,841▼ 0.31% / LTC$94.05▲ 1.14% / SOL$221.60▲ 3.02% / USDT·TRC20$1.0000 / USDC·ERC20$0.9999 / XMR$168.42▼ 0.18% / TON$6.02▲ 0.47% / XRP$2.41▼ 0.09% / GAS·ETH18 gwei / Indicative prices · illustrative ticker / No-KYC · non-pooled · 0.4% flat / BTC$68,420▲ 0.82% / ETH$3,841▼ 0.31% / LTC$94.05▲ 1.14% / SOL$221.60▲ 3.02% / USDT·TRC20$1.0000 / USDC·ERC20$0.9999 / XMR$168.42▼ 0.18% / TON$6.02▲ 0.47% / XRP$2.41▼ 0.09% / GAS·ETH18 gwei / Indicative prices · illustrative ticker / No-KYC · non-pooled · 0.4% flat
OrbChain
UTC 18:47:02 Get started →
§ Product · 2026-08-17

Paying 100 people
in one API call.

Affiliate runs, weekly payroll, tournament prize pools, creator revenue — the shape is always the same: a list of addresses and amounts that has to go out correctly, where "correctly" means nobody gets paid twice, nobody gets silently skipped, and a crashed script at item 47 doesn't force a human to diff bank statements against a CSV. That's a harder problem than sending one payment 100 times. Here's how the batch payout API solves it.

The naive loop, and why it breaks

The obvious implementation — for row in csv: send(row) — fails in exactly the ways that cost money. The script dies mid-run: which rows went out? You rerun it: everyone before the crash gets paid twice. One address is malformed: does the loop stop (everyone after waits) or continue (the failure scrolls past)? Every team that runs payouts long enough writes this script, and every team eventually replaces it after an expensive week.

The fix isn't cleverer scripting — it's moving the bookkeeping into the payment layer, where it can be transactional.

One request, per-item accounting

curl https://orbchain.io/v1/payout/batch \
  -X POST \
  -H "payout_api_key: YOUR_PAYOUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "currency": "USDT_TRC20", "address": "T…", "amount": "120.00",
        "external_id": "payrun-2026-08-17-alice" },
      { "currency": "USDT_TRC20", "address": "T…", "amount": "85.50",
        "external_id": "payrun-2026-08-17-bob" }
    ],
    "totp_code": "492013"
  }'

Up to 100 items per call, and the response is a ledger, not a boolean — every item comes back with its own verdict:

{
  "summary": { "total": 2, "created": 1, "idempotent": 0, "failed": 1 },
  "results": [
    { "index": 0, "external_id": "payrun-2026-08-17-alice",
      "track_id": "cmo79…", "status": "created", "error": null },
    { "index": 1, "external_id": "payrun-2026-08-17-bob",
      "track_id": null, "status": "failed", "error": "insufficient_balance" }
  ]
}

A bad item fails alone — the rest of the batch proceeds. Fix Bob's problem, resubmit the whole file, and everyone who already succeeded comes back idempotent instead of getting paid again. Which brings us to the load-bearing field.

external_id is the whole design

Give every item a stable identifier from your system — a payroll row, an affiliate period, an invoice number. The API treats it as an idempotency key: the same external_id can never create a second payout, no matter how many times the batch is submitted. Duplicates within one batch are rejected up front, before anything executes.

This is what turns your payout job into something safe to run badly. Crash at item 47 and resubmit all 100: items 1–47 return idempotent, 48–100 execute. No diffing, no state file, no "did-we-pay-Bob" spreadsheet. The retry loop your naive script needed a human for is now just… a retry loop.

Security shaped for batches

With 2FA enabled, one totp_code authorizes the whole batch — a deliberate middle ground between "a code per item" (unusable at 100 items) and "no step-up on the biggest money movement you make" (unacceptable). Combine that with a payout-scoped API key locked to your server's IPs and the operational surface stays small even though the dollar amounts aren't. One practical note for brand-new accounts: the very first payout on an account has a short holding period before it broadcasts — plan your first live run accordingly rather than discovering it on payday.

What happens after 200 OK

Each created item is an ordinary payout with a track_id: it broadcasts, confirms, and fires a signed payout.confirmed webhook — or payout.failed, in which case the balance is re-credited atomically and the webhook carries the reason. Poll GET /v1/payout/:track_id if you'd rather pull than listen. Either way, reconciliation is per-item, tied to your own external_id, with a tx hash at the end of every happy path.

And if your list is the same list every week — cash out to the same treasury address when the balance crosses a threshold — you may not need batches at all: auto-payout rules and payout schedules do standing movements without any API call. Batches are for when the recipients are many and the amounts change; schedules are for when they don't. Between the two, the CSV-and-prayer script finally retires.

§ Keep reading

Related posts.

Hand-picked
Same rails, next questions