Start with shared fields, then the Mexico or Colombia guide. The CFDI, RFC, SAT catalog, and payment-complement examples below describe Mexico; they are not universal country requirements.
Overview
An income invoice batch issues up to 1,000 CFDIs de ingreso from a single request. Each item of the batch is exactly the body you would send toPOST /invoices/income. gigstack validates every item right away, answers 202 with the batch, and stamps the accepted items in the background.
Use it when your company issues one invoice per sale of its own and has many of them at once: the day’s orders from a store, a monthly subscription run, or a backlog after an outage.
Base path: https://api.gigstack.io/v2/invoices/income/batch
A batch goes through three steps:
- Create.
POST /invoices/income/batchwith anIdempotency-Keyheader and{ "invoices": [ … ] }. Items that fail validation are listed inrejected; the rest are accepted. - Follow. Poll
GET /invoices/income/batch/{id}, or subscribe a webhook toinvoice_batch.completed, untilstatusiscompleted. - Read the results. Check
result, then page throughGET /invoices/income/batch/{id}/itemsfor each invoice’suuidorerror.
Key Features
- Up to 1,000 invoices per request - send more requests for more (see Limits)
- The single endpoint’s rules, unchanged - each item is validated and stamped by the same code as
POST /invoices/income, so it succeeds or fails exactly as a single call would - Item-level rejections - an invalid item is rejected with a reason, and the other items still go ahead
- Safe retries at two levels - the
Idempotency-Keyheader makes a retried request return the same batch, and each item’sidempotency_keymakes sure an invoice is never issued twice (see Idempotency) - Automatic retries - an item that hits a temporary PAC failure is retried by gigstack, up to 6 attempts
- A clear outcome - a finished batch reports
result:completed,partially_completedorfailed - Polling or webhook -
GET /invoices/income/batch/{id}at any time, orinvoice_batch.completedwhen it finishes
When to Use It
Sales to the general public. If most of your sales go to customers who don’t ask for an invoice, you may not need one CFDI per sale at all. gigstack already produces the monthly global invoice (factura global) that groups those sales: record each sale as a receipt, and the receipts nobody invoiced are included in the end-of-month global invoice automatically (see End-of-Month Global Invoicing and Global Invoices). Use a batch for the customers who do need their own CFDI.
Not supported by batches:
- File uploads (CSV/XLSX). The batch takes JSON only.
- Egress invoices (notas de crédito) and payment complements (complementos de pago). Use
POST /invoices/egressandPOST /invoices/paymentone by one. - Returning files.
return_filesin an item is ignored; download the files later withGET /invoices/{id}/files.
Idempotency
A batch uses two keys, and they protect different things.The batch key (Idempotency-Key header)
8-128 characters from A-Z a-z 0-9 . _ : -, for example sales-2026-09-29-part-1. The batch id is derived from your team, the credential’s mode and this key, so:
- The first request with a key creates the batch:
202. - A later request with the same key and the same body returns that batch as it is now:
200. Nothing is created again. If the first request died halfway through creating the batch, the retry finishes creating it. - The same key with a different body is refused:
409 idempotency_key_reused. Bodies are compared as sent, including the order of keys, so a retry must resend the exact same JSON. - The same key used with a test key and with a live key names two different batches.
POST times out or answers 500, send it again unchanged with the same key.
The invoice key (idempotency_key in each item)
This is the same idempotency_key that POST /invoices/income accepts, for example your order id. It must be present in every item and unique within the batch (up to 256 characters; surrounding spaces are trimmed).
gigstack claims the key before it charges a credit or stamps, so:
- An invoice already issued under the key, by an earlier batch or by a single
POST /invoices/income, is not issued again. The item endsduplicate, with the existing invoice’suuid, and no credit is charged. - If the PAC’s answer is lost, the next attempt sends the same XML with the same folio. The PAC either stamps it then, or reports the stamp it already made. It is never stamped twice. When the PAC cannot say, the item ends
needs_reviewinstead of risking a second CFDI. - A rejection by the SAT frees the key, so you can fix the data and send the invoice again under the same key.
Idempotency-Key: everything already issued comes back as duplicate.
Keys are scoped to your team and to the credential’s mode.
Limits
- 1,000 invoices per request. More is
400 too_many_items. For 5,000 invoices, send five batches, each with its ownIdempotency-Key. There is no limit on the number of batches. - Body size. Keep the JSON body under 10 MB.
- Throughput. A team’s items are stamped about 10 at a time across all its batches, so several batches from one team don’t go faster than one. Expect up to roughly 10,000 invoices an hour, and less when other teams are stamping batches at the same time.
- Credits. Each stamped item consumes one credit, as a single call would. When the team’s limit is reached, the remaining items fail with
credit_limit_reached.
Batch Status
A batch whose items were all rejected up front has nothing to stamp. It completes at once, usually already in the
202 response, with result: "failed", and still sends invoice_batch.completed.
Batch Result
status: "completed" only means there is nothing left to do. To know how it went, read result, not status. It is null while the batch is processing.
Item Status
Endpoints
Create a Batch
Body:
{ "invoices": [ … ] }, 1 to 1,000 items. Each item is the POST /invoices/income body with a required idempotency_key. livemode, team and owner in an item are ignored; they come from the credential.
Idempotency-Key and body answers 200 with the same batch, as it is now.
Rejected items
Each item is checked on its own when the batch is created, with no lookups. A rejected item is never stamped or charged, and doesn’t stop the others.index is its position in invoices, from 0.
What needs the database or the PAC (a client id that doesn’t exist, a SAT rejection) can’t be known up front. Those items are accepted and end
failed.
Get a Batch
counts fills in as items finish, and result and completed_at are set when the batch completes.
404 not_found.
List Batch Items
rejected.
data.data and the cursor at data.next.
data.next back as next while data.has_more is true. The cursor is the last index of the page, so pages never overlap or skip items while statuses change.
Item errors
error is set only on failed and needs_review items. Its code is what POST /invoices/income would have answered for the same body: a SAT/PAC code such as CFDI40147, or one of gigstack’s (SAT_NOT_CONNECTED, CSD_VALIDATION_ERROR, PAC_UNAVAILABLE, PAC_OUTCOME_UNKNOWN, STAMP_NEEDS_REVIEW, …). Look it up in the CFDI errors catalog. Batches add a few of their own:
message is at most 500 characters. Stamping messages are in Spanish, as on the single endpoint.
Finding the invoices. invoice_id reads the invoice with GET /invoices/income/{id}, and GET /invoices/{id}/files returns its PDF and XML. You can also find an invoice by your own key with GET /invoices/income?idempotency_key=….
Following Progress
Polling. PollGET /invoices/income/batch/{id} every 30-60 seconds. counts.queued goes down to 0 as items finish; the batch is done when status is completed.
Webhook. Subscribe a webhook to invoice_batch.completed. It is sent once, when the batch completes, and carries the batch id, its counts and its result:
secret (see Verifying Signatures), but it is sent only once and never retried. If your endpoint is down at that moment, you miss it. Use the webhook to react quickly, and keep a slow poll (for example every 10 minutes) as a fallback. Note that data.rejected is a count here, while on the batch object rejected is the list.
Each invoice the batch stamps also sends the usual invoice.created event, like any other invoice. Items that fail don’t send invoice.failed, so read the items to find them.
End-to-End Example
Use Bash,curl and jq, with GIGSTACK_API_KEY loaded locally and the input files
reviewed for the intended team and mode. These scripts do not retry failed writes
automatically. An HTTP or JSON error stops the script; reconcile any ambiguous write
before starting again.
Response Objects
Batch
Item
Error Handling
The batch endpoints use the standardized envelope. Branch onerror.code, not on the message.
401, and the 403 for a revoked key or a plan without API access) come from the authentication layer with a raw { "message": … } body; see Authentication errors.
A problem with one item is never an HTTP error: it is in
rejected, or on the item as failed.
gigstack Connect. A master team’s API key with the multipleIssuerAccounts feature can create a batch for a connected team with ?team=<team id>. The batch belongs to that team: read it and its items with the same team parameter, or they answer 404. See gigstack Connect.
Best Practices
- Use your order id as each item’s
idempotency_key. It is what guarantees an order is invoiced once, across batches, retries and single calls. - Retry a request with the same
Idempotency-Keyand the exact same body. Use a new key only for a different set of invoices. - Resend failed items in a new batch, with their original
idempotency_key. Don’t mint new keys for them, or you lose the protection against issuing twice. - Never resend a
needs_reviewitem under a new key. Contact support; it may already be stamped. - Split large volumes into batches of up to 1,000. Send them one after another; a team’s items are stamped at the same pace however many batches it has.
- Try it with a test key first. Test batches and live batches are separate.
- Read
result, notstatus, and list thefaileditems when it isn’tcompleted. - Don’t rely on the webhook alone. It is sent once and never retried; keep a slow poll as a fallback.
Related Resources
- Invoices API - The single
POST /invoices/income, whose body each item uses, and its error handling - Receipts API - Sales that end up in the monthly global invoice
- Webhooks API - Subscribe to
invoice_batch.completed - Platform Payouts API - For marketplaces invoicing on behalf of their providers, not for your own sales
- CFDI Errors Reference - Stamping errors you may see in an item’s
error - Test Mode
For additional assistance, contact support@gigstack.io