> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://en.docs.api.corpx.com/baas/guias/pagar/pix-em-lote/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://en.docs.api.corpx.com/_mcp/server. # Batch PIX guide **Batch PIX** is for the case where you already have a spreadsheet — payroll, supplier payouts, commissions, refunds — and want to pay everything at once without writing a loop around `POST /pix/out`. You upload the rows, the API checks keys, duplicates, balance and limits **before any debit**, and only after your explicit acknowledgement does each row become a regular PIX out, with the same limits, policies, webhooks and statement entries as [PIX Out](/baas/guias/pagar/pix-out). > **Batch vs. single PIX** > > Use a batch when the rows already exist together (a file, a closing) and you > want one summary to approve and one report at the end. Use `POST /pix/out` > when a payment is born from an isolated event and needs a synchronous > answer. Both paths produce the same PIX and the same `pix.out.*` webhooks. ## How it works ```mermaid flowchart LR T["GET template"] --> C["POST batches
DRAFT"] C --> I["POST items/csv
PUT items"] I --> R["POST review
REVIEWING → REVIEWED"] R -->|"INVALID items"| X["GET items/export?status=invalid
fix and resend"] X --> I R -->|"acknowledged: true"| D["POST dispatch
QUEUED → PROCESSING"] D --> P["1 PixOut per item
pix.out.completed / failed"] P --> F["COMPLETED | PARTIAL_FAILED | FAILED
pix.batch.completed"] F --> Z["POST receipts (ZIP)
GET items/export?status=failed"] ``` Three design decisions apply to the whole surface: * **API only (M2M).** The routes require a credential with the `pix_out.create` scope (or legacy `api2/write`). A human user token cannot create or dispatch a batch; the Portal and Internet Banking do not expose this surface. A read-only credential (`read`) can **follow** batches (`GET`), download the template and receipts, but cannot create, edit or dispatch. * **Two-level enablement.** CorpX turns on the `pix_batch` feature for the tenant **and** sets `pixBatch.enabled = true` in the account policy. Without the first the API answers `403 feature_disabled`; without the second, `403 pix_batch_account_disabled`. Ask your account manager to enable it, listing the accounts that will pay in batches. * **Nothing leaves without review and acknowledgement.** Dispatch requires a `REVIEWED` batch, zero invalid items, enough balance and `acknowledged: true` in the body. The review summary exists to be shown to whoever approves. ### Prerequisites | Item | Where | | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | | M2M credential with the `pix_out.create` scope, restricted to the paying accounts | [Authentication](/baas/guias/autenticacao/oauth2) | | `pix_batch` feature on the tenant and `pixBatch.enabled` in the account policy (enabled by CorpX) | [Policies](/baas/guias/autenticacao/politicas) | | Webhook subscription for `pix.batch.completed` and, for per-row detail, `pix.out.completed` / `pix.out.failed` | [Webhooks](/baas/guias/conta/webhooks) | | If the account uses locks or a transaction PIN, the cash-out headers on dispatch | [Account security](/baas/guias/autenticacao/seguranca-da-conta) | ## Quick start Five calls take a file from zero to paid. In the examples `$BASE` is `https://tenant.api.corpx.com/v1/accounts/{accountId}` and the `Authorization: Bearer $TOKEN` and `X-Tenant-Id` headers are implied. #### Download the template ```bash curl "$BASE/pix/batches/template" -o pix-lote-modelo.csv ``` #### Create the batch ```bash curl -X POST "$BASE/pix/batches" \ -H "Idempotency-Key: payroll-2026-09" \ -H "Content-Type: application/json" \ -d '{"name": "September payroll"}' # 201 → {"batchId": "pxb_…", "status": "DRAFT", …} ``` #### Upload the rows ```bash curl -X POST "$BASE/pix/batches/$BATCH/items/csv" \ -H "Content-Type: text/csv" \ --data-binary @payroll-september.csv # 200 with the batch and its items; 400 with errors[] if any row is wrong (nothing is saved) ``` #### Review ```bash curl -X POST "$BASE/pix/batches/$BATCH/review" # 202, status REVIEWING curl "$BASE/pix/batches/$BATCH" # repeat until status REVIEWED; read "review" ``` #### Approve and dispatch ```bash curl -X POST "$BASE/pix/batches/$BATCH/dispatch" \ -H "Content-Type: application/json" \ -d '{"acknowledged": true}' # 202, status QUEUED. Wait for the pix.batch.completed webhook. ``` ## Routes All under `/v1/accounts/{accountId}/pix/batches`. The **Scope** column is what the credential needs; `read` follows, `pix_out.create` operates. | Method and route | Scope | What it does | | --------------------------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------- | | `GET /template` | `read` | Downloads `pix-lote-modelo.csv` with the columns and two example rows. | | `POST /` | `pix_out.create` | Creates a `DRAFT` batch. `Idempotency-Key` required. | | `GET /` | `read` | Lists the account's batches (`status`, `page`, `pageSize`). | | `GET /{batchId}` | `read` | Batch, `review`, `statistics` and paginated items (`status`, `reviewStatus`, `page`, `pageSize`). | | `PUT /{batchId}/items` | `pix_out.create` | Adds 1 to 50 items as JSON. | | `POST /{batchId}/items/csv` | `pix_out.create` | Adds items from a CSV (raw body, up to 5 MiB). | | `DELETE /{batchId}/items/{itemId}` | `pix_out.create` | Removes an item. | | `POST /{batchId}/review` | `pix_out.create` | Starts the asynchronous review. | | `POST /{batchId}/dispatch` | `pix_out.create` | Dispatches the payments. Requires `acknowledged: true`. | | `GET /{batchId}/items/export` | `read` | Correction CSV (`status=failed` default, `invalid`, `completed`, `all`; `separator=comma`). | | `GET /{batchId}/items/{itemId}/receipt` | `read` | PDF receipt of a `COMPLETED` item. | | `POST /{batchId}/receipts` | `pix_out.create` or `exports.create` | Job that builds the ZIP with every receipt. | The field-by-field reference is in [API Reference → Batch PIX](/baas/referencia). ## 1. The file `GET .../pix/batches/template` returns `pix-lote-modelo.csv`: UTF-8 with BOM and `;` as separator, which Excel in Portuguese locales opens directly. Its content is exactly this: ```csv identifier;amount;description;pixKeyType;pixKey;receiverName;receiverDocument;bankIspb;bankCode;branch;accountNumber;accountType folha-set-001;1436.85;Pagamento setembro;CPF;12345678909;;;;;;; folha-set-002;2500.00;Fornecedor;;;EMPRESA EXEMPLO LTDA;12345678000195;60701190;341;0001;123456;CHECKING ``` Each row is **one** PIX and fills **one** of the two receiver blocks: | Column | Rule | | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `identifier` | Required. Your business key, up to 38 characters, unique within the batch **and** among the account's live payments (same rule as the PIX out `identifier`). Becomes the payment's `identifier` in the statement. | | `amount` | Required and positive. Accepts `1234.56`, `1.234,56` or `R$ 1.234,56`. | | `description` | Optional, up to 140 characters. Travels with the PIX as its description. | | `pixKeyType`, `pixKey` | **Key mode.** `pixKey` selects the mode; `pixKeyType` (`CPF`, `CNPJ`, `EMAIL`, `PHONE`, `EVP`) is optional and detected from the format. Receiver name, document and bank are resolved in DICT during review. | | `receiverName`, `receiverDocument`, `bankIspb`, `bankCode`, `branch`, `accountNumber`, `accountType` | **Bank account mode.** Name, CPF/CNPJ, ISPB **or** COMPE code, branch, account with check digit and type (`CHECKING`, `SAVINGS`, `PAYMENT`, `SALARY`; empty = `CHECKING`). | > **Parser rules that save rework** > > * The header is matched by **name**, regardless of order or case; columns > you do not use can be left empty or omitted. > * `;` and `,` are detected automatically; quoting follows the CSV standard. > * The `errorCode` and `errorMessage` columns of the correction file are > ignored on input: the exported file can be uploaded back as is. > * A row with `pixKey` **and** bank account data at the same time is > rejected — pick one mode per row. ## 2. Create the batch ```bash curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/pix/batches" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" \ -H "Idempotency-Key: payroll-2026-09" \ -H "Content-Type: application/json" \ -d '{"name": "September payroll"}' ``` * `Idempotency-Key` (up to 128 characters) is **required**; repeating the same key on the same account returns the existing batch with `200` instead of creating another. Use something that identifies the closing (`payroll-2026-09`), not a random UUID — that way a retry on your side never opens a duplicate batch. * `name` is optional (up to 120 characters) and shows up in listings and in the receipts' `resumo.csv`. `201` response: ```json { "batchId": "pxb_3f9c2a1b7d4e5f60718293a4b5c6d7e8", "name": "September payroll", "status": "DRAFT", "totalItems": 0, "totalAmount": "0.00", "completedItems": 0, "failedItems": 0, "limits": { "maxItems": 50, "source": "default" }, "statistics": { "total": 0, "pending": 0, "processing": 0, "completed": 0, "failed": 0, "reviewedOk": 0, "invalid": 0, "totalAmount": "0.00", "validAmount": "0.00", "completedAmount": "0.00", "failedAmount": "0.00", "progressPercentage": 0 }, "items": [], "page": 1, "pageSize": 50, "totalPages": 0, "hasNext": false, "createdAt": "2026-09-30T13:00:00Z", "updatedAt": "2026-09-30T13:00:00Z" } ``` `limits.maxItems` is the maximum size of this batch **on this account** and `limits.source` says where it came from (`default` = 50, `tenant` or `account`, up to 5,000). Monetary values are always **decimal strings** with two places (`"1436.85"`), never floating-point numbers. ## 3. Upload the items You may mix both methods in the same batch, across as many calls as you need, while the batch is `DRAFT` or `REVIEWED`. #### CSV ```bash curl -X POST ".../pix/batches/{batchId}/items/csv" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" \ -H "Content-Type: text/csv" \ --data-binary @payroll-september.csv ``` The body is the raw file (up to 5 MiB; above that, `413`). The upload is **atomic**: if any row is invalid the response is `400 pix_batch_csv_invalid` with the list of problems and **nothing is saved**. ```json { "errorCode": "pix_batch_csv_invalid", "message": "one or more rows are invalid; nothing was saved", "errors": [ { "line": 3, "field": "amount", "message": "must be a positive amount" }, { "line": 7, "field": "pixKey", "message": "use either pixKey or the bank account columns, not both" }, { "line": 9, "field": "identifier", "message": "duplicated identifier (first seen on line 4)" } ], "docs": "https://docs.api.corpx.com/baas/guias/referencia/erros#pix_batch_csv_invalid" } ``` `line` counts the header as line 1, so `3` is the second data row — the same number Excel shows. A file with more rows than `limits.maxItems` is `400 pix_batch_too_large`. #### JSON ```bash curl -X PUT ".../pix/batches/{batchId}/items" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" \ -H "Content-Type: application/json" \ -d '{ "items": [ { "identifier": "payroll-sep-001", "amount": "1436.85", "description": "September payroll", "key": "12345678909", "keyType": "CPF" }, { "identifier": "payroll-sep-002", "amount": 2500.00, "description": "Supplier", "name": "EMPRESA EXEMPLO LTDA", "documentNumber": "12345678000195", "bankIspb": "60701190", "accountBranch": "0001", "accountNumber": "123456", "accountType": "CHECKING" } ] }' ``` **1 to 50 items per call**, with the same vocabulary as `POST /pix/out`: `identifier`, `amount` (string or number), `description`, and `key`/`keyType` **or** `name`, `documentNumber`, `bankIspb` or `bankCode`, `accountBranch`, `accountNumber`, `accountType`. For large batches, send chunks of 50 in sequence — every call returns the updated batch. An empty chunk, more than 50 items, or any invalid item is `400 pix_batch_chunk_invalid` with the same `errors[]` envelope as the CSV (`line` is the 1-based position in the array) and nothing is saved. An `identifier` repeated within the chunk itself is flagged too. ### Rules common to both methods * **Per-row idempotency.** Repeating an `identifier` with the **same** content is ignored (no duplicate, no error). Repeating it with **different** content is `409 pix_batch_item_conflict` and the whole call is refused — fix the file instead of relying on overwrites. * **Removing.** `DELETE .../items/{itemId}` removes an item; the `itemId` (`pxi_…`) comes in the batch's `items[]`. * **Editable states.** Only `DRAFT` and `REVIEWED` accept items or removals. Any change to a `REVIEWED` batch sends it back to `DRAFT` and resets the review: you must review again before dispatching. While `REVIEWING` or after dispatch, `409 pix_batch_not_editable`. * **Size.** The batch's item count never exceeds `limits.maxItems`; the call that would cross the ceiling is refused as a whole with `400 pix_batch_too_large`. ## 4. Review ``` POST /v1/accounts/{accountId}/pix/batches/{batchId}/review → 202, status REVIEWING ``` The review is asynchronous and runs **before any debit**. Repeating the `POST` while `REVIEWING` just returns the current state. Per item, in this order: | Check | `reviewError.errorCode` | | ------------------------------------------------------------------------------------ | -------------------------------------------------- | | Positive amount | `invalid_amount` | | `identifier` already used by a live payment on the account (including another batch) | `identifier_conflict` | | Amount above the account's per-transaction limit | `limit_exceeded_transaction` | | PIX key missing, inactive or malformed (DICT lookup) | `key_not_found`, `key_inactive`, `invalid_pix_key` | | DICT unavailable after 3 attempts | `dict_lookup_failed` | In key mode, DICT resolves the receiver's **name, document and bank**, which then appear in `items[].receiver` — that is what you show to the approver. Lookups use the account's 24 h cache and **count against the DICT lookup window** like `GET /pix/key/{pixKey}`; read [PIX key lookups](/baas/guias/pagar/consulta-dict) before reviewing thousands of new keys. Bank-account items are accepted as sent; destination account validation happens at the settlement bank during execution. Follow with `GET .../pix/batches/{batchId}`. When `status` becomes `REVIEWED`, `review` carries the summary: ```json { "reviewedAt": "2026-09-30T13:09:30Z", "totalItems": 120, "validItems": 118, "invalidItems": 2, "totalAmount": "143685.00", "validAmount": "141200.00", "balance": { "available": "150000.00", "sufficient": true, "checked": true }, "limits": [ { "window": "daily_out", "enforced": true, "limit": "500000.00", "used": "12000.00", "available": "488000.00", "sufficient": true }, { "window": "monthly", "enforced": false, "limit": "0.00", "used": "0.00", "available": "0.00", "sufficient": true } ], "warnings": [] } ``` * `balance` and `limits` are a **snapshot**. Balance is checked again at dispatch and every PIX goes through the per-window limits during execution. `limits[].window` is `daily_out` (6am–8pm) or `nightly_out` (8pm–6am) depending on the review time, plus `monthly`; `enforced: false` means the account has no ceiling configured for that window. * `balance.checked: false` means the settlement bank did not answer; the warning goes to `warnings[]` and does not block. * `warnings[]` **does not block** the acknowledgement (e.g. insufficient balance right now — funds may arrive before dispatch). `INVALID` items **do block**. * If the review cannot run, the batch goes back to `DRAFT` and `review` carries an `error` field; just call `review` again. A rejected item looks like this in `items[]`: ```json { "itemId": "pxi_9b1c…", "line": 17, "identifier": "payroll-sep-017", "mode": "KEY", "amount": "980.00", "description": "September payroll", "status": "PENDING", "reviewStatus": "INVALID", "receiver": { "name": "", "documentNumber": "", "keyType": "EMAIL", "key": "former-employee@company.com", "bankCode": "", "bankIspb": "" }, "reviewError": { "errorCode": "key_not_found", "message": "chave PIX não encontrada no DICT" }, "createdAt": "2026-09-30T13:02:11Z", "updatedAt": "2026-09-30T13:09:28Z" } ``` To fix, download only the rejected rows, adjust and upload again (the same `identifier` with new content is refused — remove the item first with `DELETE`, or use a new `identifier`), then review once more: ``` GET /v1/accounts/{accountId}/pix/batches/{batchId}/items/export?status=invalid GET /v1/accounts/{accountId}/pix/batches/{batchId}?reviewStatus=invalid ``` ## 5. Dispatch ```bash curl -X POST ".../pix/batches/{batchId}/dispatch" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" \ -H "X-Acting-Document: 12345678909" \ -H "X-Transaction-Pin: 123456" \ -H "Content-Type: application/json" \ -d '{"acknowledged": true}' ``` `acknowledged: true` is the statement that the review summary was read and approved — record on your side who approved and when. Without it the API answers `409 pix_batch_acknowledgement_required`. Dispatch also requires, in this order: 1. A `REVIEWED` batch (`409 pix_batch_not_reviewed`). 2. At least one item (`409 pix_batch_empty`) and a total within `limits.maxItems` (`400 pix_batch_too_large`). 3. No `INVALID` item and every item reviewed (`409 pix_batch_has_invalid_items` / `409 pix_batch_not_reviewed`). 4. Available balance **greater than or equal to the batch total** at dispatch time (`422 pix_batch_insufficient_balance` — nothing is started). > **Dispatch moves money** > > Dispatch is a cash-out route: the account's outgoing locks, the transaction > PIN and the `X-Acting-Document` / `X-Transaction-Pin` / `X-Acting-Ip` > headers apply exactly as in `POST /pix/out` (see > [Account security](/baas/guias/autenticacao/seguranca-da-conta)). The > headers are only needed when the account has a PIN or locks configured. > After the `202` there is **no cancellation**: items already in flight run > until they settle or fail. `202` response with `status: QUEUED` and `dispatchedAt`. Re-dispatching an already dispatched batch is idempotent (`200` with the current state), so a retry after a network timeout is safe. ## 6. Follow up Each item becomes its own `PixOut.Workflow` — the same one that serves `POST /pix/out` — with the row's `identifier` and idempotency `{batchId}_{itemId}`. It goes through the same policies, per-window limits and statement entry as a single PIX, and emits the same `pix.out.completed`, `pix.out.failed` or `pix.out.timeout`. The batch pays up to 5 items in parallel (CorpX setting) and only moves on when the settlement bank answers; a slow item does not hold up the others. ### Batch states | `status` | Meaning | Editable? | | ---------------- | ------------------------------------------------------------------- | --------- | | `DRAFT` | Receiving items. | Yes | | `REVIEWING` | Review in progress. | No | | `REVIEWED` | Reviewed; ready for acknowledgement. Any edit goes back to `DRAFT`. | Yes | | `QUEUED` | Dispatch accepted; waiting for the processor. | No | | `PROCESSING` | Items being paid. `statistics` advances. | No | | `COMPLETED` | Every item settled. | No | | `PARTIAL_FAILED` | Some settled, some failed. Download the failures export. | No | | `FAILED` | No item settled. | No | ### Item states | `status` | `reviewStatus` | When | | ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `PENDING` | `PENDING` | Just added, not reviewed yet. | | `PENDING` | `OK` / `INVALID` | Reviewed; `INVALID` carries `reviewError`. | | `PROCESSING` | `OK` | PixOut in flight. | | `COMPLETED` | `OK` | Settled: `paymentId`, `endToEndId`, `transactionId`, `completedAt`. | | `FAILED` | `OK` | Rejected or expired: `error.errorCode` and `error.message` with the single-PIX vocabulary (`insufficient_funds`, `limit_exceeded_daily`, `timeout`, …). | `GET .../pix/batches/{batchId}` returns `statistics` and the paginated items (`page`, `pageSize` up to 200), filterable by `status` (`pending`, `processing`, `completed`, `failed`) and `reviewStatus` (`pending`, `ok`, `invalid`): ```json { "batchId": "pxb_3f9c2a1b7d4e5f60718293a4b5c6d7e8", "status": "PROCESSING", "totalItems": 120, "totalAmount": "141200.00", "completedItems": 87, "failedItems": 2, "statistics": { "total": 120, "pending": 0, "processing": 31, "completed": 87, "failed": 2, "reviewedOk": 120, "invalid": 0, "totalAmount": "141200.00", "validAmount": "141200.00", "completedAmount": "102340.00", "failedAmount": "1960.00", "progressPercentage": 74.17 }, "items": [ { "itemId": "pxi_1a2b…", "line": 1, "identifier": "payroll-sep-001", "mode": "KEY", "amount": "1436.85", "description": "September payroll", "status": "COMPLETED", "reviewStatus": "OK", "receiver": { "name": "JOAO ANTONIO DA CONCEICAO", "documentNumber": "12345678909", "keyType": "CPF", "key": "12345678909", "bankCode": "341", "bankIspb": "60701190" }, "paymentId": "pay_1f2e3d4c", "endToEndId": "E5087192120260930131200abcdef0123", "transactionId": "9f8e7d6c-5b4a-4321-8765-43210fedcba9", "completedAt": "2026-09-30T13:12:01Z", "createdAt": "2026-09-30T13:02:11Z", "updatedAt": "2026-09-30T13:12:01Z" } ], "page": 1, "pageSize": 50, "totalPages": 3, "hasNext": true, "reviewedAt": "2026-09-30T13:09:30Z", "acknowledgedAt": "2026-09-30T13:10:02Z", "dispatchedAt": "2026-09-30T13:10:02Z", "createdAt": "2026-09-30T13:00:00Z", "updatedAt": "2026-09-30T13:12:01Z" } ``` ### Closing webhook At the end the API sends **`pix.batch.completed`**. It closes the batch and **does not replace** the per-item events: to reconcile row by row use `pix.out.completed` / `pix.out.failed` (the event's `identifier` is your row's) or the item list. ```json { "id": "pix-batch-pxb_3f9c2a1b7d4e5f60718293a4b5c6d7e8-completed", "type": "pix.batch.completed", "occurredAt": "2026-09-30T13:12:40.000Z", "schemaVersion": "1.0", "environment": "production", "tenantId": "tenant-yourcompany", "accountId": "acc_123456", "data": { "batchId": "pxb_3f9c2a1b7d4e5f60718293a4b5c6d7e8", "accountId": "acc_123456", "status": "PARTIAL_FAILED", "statistics": { "total": 120, "completed": 118, "failed": 2, "totalAmount": "141200.00", "completedAmount": "139240.00", "failedAmount": "1960.00" } } } ``` Subscribe with `authType: HMAC`, validate `X-Signature` over the raw bytes and deduplicate by `id` — details in [Webhooks](/baas/guias/conta/webhooks). > **Webhook first, polling as a fallback** > > Prefer reacting to `pix.batch.completed`. If you need polling (a progress > bar, for instance), call `GET .../pix/batches/{batchId}` every 10–30 s and > read only `status` and `statistics`; do not list the items on every cycle. ## 7. Fix and resend ``` GET /v1/accounts/{accountId}/pix/batches/{batchId}/items/export?status=failed ``` Returns, in the **same format as the template**, only the rows that failed during execution or were marked invalid in the review, with two extra columns at the end: `errorCode` and `errorMessage`. The `X-Total-Rows` header tells how many rows came back. ```csv identifier;amount;description;pixKeyType;pixKey;receiverName;receiverDocument;bankIspb;bankCode;branch;accountNumber;accountType;errorCode;errorMessage payroll-sep-017;980.00;September payroll;EMAIL;former-employee@company.com;;;;;;;;key_not_found;chave PIX não encontrada no DICT payroll-sep-044;1200.00;September payroll;;;MARIA DA SILVA;98765432100;;001;1234;56789-0;CHECKING;limit_exceeded_daily;limite diário de saída excedido ``` Fix it and upload the file **as is** into a **new** batch — the parser ignores the error columns. An `identifier` that failed can be reused; one that settled cannot (the review would flag it as `identifier_conflict`), which protects you from paying the same row twice. | `status=` | Rows included | | ------------------ | -------------------------------------------------------------------------- | | `failed` (default) | Failed during execution **or** rejected in the review. | | `invalid` | Only the ones rejected in the review (use while the batch is `REVIEWED`). | | `completed` | Only the settled ones — handy to reconcile by `endToEndId`. | | `all` | Every row, with empty `errorCode`/`errorMessage` where there was no error. | `separator=comma` swaps `;` for `,` for English-locale tools. ## 8. Receipts Receipts are CorpX-standard PDFs (tenant logo when configured, payer, receiver with masked document, amount, description, `identifier`, `endToEndId`, settlement time in Brasília time). **One item** — direct `application/pdf` response, only for `COMPLETED` items (`409 pix_batch_receipt_unavailable`): ``` GET /v1/accounts/{accountId}/pix/batches/{batchId}/items/{itemId}/receipt ``` **Whole batch** — for finished batches (`COMPLETED`, `PARTIAL_FAILED` or `FAILED` with at least one settled item; otherwise `409 pix_batch_no_receipts`): ```bash curl -X POST ".../pix/batches/{batchId}/receipts" \ -H "Authorization: Bearer $TOKEN" -H "X-Tenant-Id: tenant-yourcompany" ``` ```json { "exportId": "exp_7c1d2e3f-…", "accountId": "acc_123456", "batchId": "pxb_3f9c2a1b7d4e5f60718293a4b5c6d7e8", "type": "pix_batch_receipts", "format": "zip", "status": "PENDING", "receipts": 118, "download": "/v1/accounts/acc_123456/exports/exp_7c1d2e3f-…/download", "createdAt": "2026-09-30T13:15:00Z" } ``` The ZIP is built in the background. Call the route in `download`: while the job runs it answers `409 not_ready`; when done, it returns a presigned `downloadUrl` (valid for `expiresIn` seconds) and `checksumMd5`. The archive holds one PDF per settled item and a `resumo.csv` with the columns `linha`, `identificador`, `destinatario`, `valor`, `endToEnd`, `data`, `arquivo` and `erro` (filled only when a PDF could not be rendered). PDFs follow the pattern `{YYYY-MM-DD}_{HHMM}_PIX_{amount}_{receiver}_{identifier}_{endToEnd}.pdf` — for example `2026-09-30_1312_PIX_1436-85_JOAO-ANTONIO-DA-CONCEICAO_payroll-sep-001_E5087192120260930131200abcdef0123.pdf` — without accents and with a `-2`, `-3`… suffix on collision. ## Best practices * **One `Idempotency-Key` per closing.** `payroll-2026-09`, `suppliers-2026-09-30`… A retry of the create call never opens a second batch, and you can find the batch again from your own calendar. * **Deterministic, per-row unique `identifier`.** Build it from your data (`{payroll}-{employeeId}`), never from an in-memory counter. It is what ties the PIX to your system in the statement, the webhooks and the correction file — and what prevents paying the same row twice. * **Always review before asking for approval.** Show `review` (totals, balance, limits, invalid rows) to the approver and only then send `acknowledged: true`. Store who approved, when, and the `reviewedAt` that was on screen. * **Separate who builds from who dispatches.** A `read` credential can follow; only the `pix_out.create` credential dispatches. If the account uses a transaction PIN, dispatch requires the approving operator's PIN. * **Treat `PARTIAL_FAILED` as routine, not an exception.** Deleted keys and closed accounts happen in any large payroll. Download `items/export?status=failed`, fix, and upload a new batch; do not redo the whole batch. * **Reconcile by `identifier` and `endToEndId`.** Each item's `pix.out.completed` and its row in `items[]` carry both; the `endToEndId` is the proof of payment with the Central Bank. * **Respect the limit windows.** `review` shows `daily_out`/`nightly_out` and `monthly`. A batch larger than the day's available limit is not blocked at dispatch — the excess items fail during execution with `limit_exceeded_*`. Split into per-day batches or ask for a limit adjustment. * **Large batches.** For thousands of rows, upload by CSV (one call) instead of dozens of JSON chunks, and ask CorpX for the right `maxItems` before the closing. ## FAQ #### Can I cancel a batch after dispatch? No. After the `202` the items are already executing at the settlement bank. What you control is the moment of acknowledgement: until `dispatch` the batch is just a draft and can be edited or simply abandoned in `DRAFT`/`REVIEWED`. #### What happens if I edit a REVIEWED batch? It goes back to `DRAFT` and every item returns to `reviewStatus: PENDING`. You must call `review` again. This applies to adding, removing or resending an item — the review always reflects the current content. #### Balance was enough at review time but dispatch returned 422. Why? `review.balance` is a snapshot; another payment may have left between the review and the acknowledgement. Dispatch checks the balance again and refuses **without starting anything** if it is below the batch total. Top up the account or remove rows and review again. #### Why did an item pass review and fail during execution? The review validates what can be validated without moving money: key, duplicates, amount, per-transaction limit, balance and windows in aggregate. Execution adds the settlement bank's and destination bank's rules (closed account, daily limit consumed by other payments, account policy). The error code is the same as for a single PIX and comes in `error.errorCode`. #### Can I reuse an identifier? An item `identifier` that **failed** can be used in a new batch. One that **settled** cannot: the review flags `identifier_conflict`, because the account has a live payment with that identifier. That is the duplicate payment protection — do not work around it with random suffixes. #### Do batch items show up in the statement? Yes, as individual PIX out entries, each with its row's `identifier`. There is no aggregated batch entry; `GET .../pix/batches/{batchId}` and the receipts' `resumo.csv` are the consolidated view. #### Does the review consume DICT quota? Yes, one lookup per distinct key not present in the account's 24 h cache, counted in the same window as `GET /pix/key/{pixKey}`. Keys repeated within the batch and keys looked up recently do not trigger a new lookup. Bank account rows involve no DICT lookup. #### How do I test before production? Create a small batch, upload two or three rows of token amounts to accounts of your own, review and dispatch — the flow and webhooks are identical to a batch of thousands. To validate only the file without paying anything, stop after `review`: the parser and the review already surface every format, key and duplicate problem. ## Errors | Code | HTTP | When | | --------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------- | | `feature_disabled` | 403 | Tenant without the `pix_batch` feature. | | `pix_batch_account_disabled` | 403 | Account without `pixBatch.enabled = true` in its policy. | | `missing_idempotency_key` | 400 | `POST /pix/batches` without `Idempotency-Key`. | | `pix_batch_not_found` / `pix_batch_item_not_found` | 404 | Batch or item does not exist on this account. | | `pix_batch_chunk_invalid` / `pix_batch_csv_invalid` | 400 | Upload rejected; `errors[]` points to row and field. `413` when the CSV exceeds 5 MiB. | | `pix_batch_too_large` | 400 | Above `limits.maxItems`. | | `pix_batch_item_conflict` | 409 | `identifier` repeated with different content. | | `pix_batch_not_editable` | 409 | Batch is `REVIEWING` or already dispatched. | | `pix_batch_empty` | 409 | Batch has no items. | | `pix_batch_not_reviewed` | 409 | Dispatch without a valid review. | | `pix_batch_has_invalid_items` | 409 | Review flagged invalid items. | | `pix_batch_acknowledgement_required` | 409 | Dispatch without `acknowledged: true`. | | `pix_batch_insufficient_balance` | 422 | Available balance below the total at dispatch. | | `pix_batch_no_receipts` / `pix_batch_receipt_unavailable` | 409 | Batch not finished or item not settled. | | `receipt_render_failed` | 500 | PDF rendering failed; retry the call. | | `workflow_unavailable` | 503 | Orchestration unavailable; retry the call. | The full list, with anchors, is in [Error handling](/baas/guias/referencia/erros). ## See also * [PIX Out](/baas/guias/pagar/pix-out) — the single payment every batch row executes. * [PIX key lookups](/baas/guias/pagar/consulta-dict) — DICT quota and cache used by the review. * [Account security](/baas/guias/autenticacao/seguranca-da-conta) — locks, transaction PIN and cash-out headers. * [Idempotency](/baas/guias/comece-aqui/idempotencia) — how `Idempotency-Key` behaves across the API. * [Webhooks](/baas/guias/conta/webhooks) — `pix.batch.completed` and the per-item `pix.out.*` events. > Pay dozens or thousands of PIX transfers from a CSV or JSON, with a pre-flight review and an explicit acknowledgement before any money moves.