---
title: "Apply one operation to a set of invoices"
method: POST
path: "/v1/companies/{company_id}/invoices/batches"
tags: ["CompanyInvoiceDelivery"]
---

# Apply one operation to a set of invoices

`POST /v1/companies/{company_id}/invoices/batches`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/companies/{company_id}/invoices/batches`
- Auth: HTTP bearer

Applies one operation to a set of invoices of this company and reports, invoice by
invoice, which succeeded and which failed.

- **Operations:** `ISSUE` issues the draft invoices; `STATUS` moves them to the
  `new_status` given in the body.
- **Limit:** up to 50 invoices per request (`invoice_ids`).
- **Not atomic:** each invoice is processed on its own, and since issuing is
  irreversible, the ones already issued stay issued if a later one fails.
- **Related:** downloading PDFs, sending email and exporting are not operations of this
  batch — use `…/invoices/pdf-archive`, `…/invoices/deliveries` and `…/invoices/exports`.

## Headers

- `Idempotency-Key` string

## Request body

- CreateInvoiceBatchRequest: Applies one operation to a set of invoices of this company. Only the operations that share a result shape live here; downloading PDFs, sending email and exporting have their own sibling sub-resources because each returns something different.
  - `operation` 'ISSUE' | 'STATUS', required: - **ISSUE**: issue the draft invoices, each one assigned its definitive number. - **STATUS**: change the status of the invoices; requires `new_status`.
  - `invoice_ids` UUID[], required
  - `new_status` 'SENT' | 'PAID': Target status. Required when `operation` is `STATUS`.
  - `payment_date` string, date: Payment date. Required when `new_status` is `PAID`.

## Response `200`

Batch completed (may include partial failures)

- BulkOperationResponse
  - `success` boolean, required
  - `data` BulkOperationResult, required: Outcome of a bulk operation processed invoice by invoice.
    - `total` integer, required: Total number of invoices processed
    - `successful` integer, required: Number of invoices processed successfully
    - `failed` integer, required: Number of invoices that failed
    - `failures` object[]: Details of the invoices that failed
      - `invoice_id` string, uuid, required: Universally Unique Identifier (UUID v4)
      - `reason` string, required: Reason for the failure
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: `INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format (a string where a number is expected, a date that does not parse, a malformed UUID, a boolean that is not `true`/`false`). The `details` object follows `FieldDeserializationError`: `field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a boolean). A property the operation does not declare is not a format error: it is ignored. A value outside an **enum**'s vocabulary is not answered here. The property is a well-formed string that names nothing the operation knows, so it is judged as content: `422` (`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details` (`field`, `invalid_value`, `allowed_values`). Other `error.code` values with this status: - `PAYMENT_DATE_REQUIRED` — `new_status: PAID` was sent without `payment_date`; add the date the invoices were paid.
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: Your account does not own or manage this company, or does not hold the required access over it. A NIF that does not exist answers the same way. That case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.
- `422`: `VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from the offending property's name (in the contract's `snake_case`, nested paths joined with a dot, e.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry per property. When the rejected value is one outside an enum's vocabulary, `details` instead follows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`). Two more codes share this status for a value that passes the contract but does not fit where it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and `VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.
- `429`: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because too many went out to that recipient in a short time. Either way, retry after the `Retry-After` seconds. `QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan for the current period (issued invoices, for instance). Retrying does not help until the quota resets or the plan changes. `error.details` carries `feature_code`, `current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until the reset when it is known.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

[API](https://skmtc.dev/beel/apis/beel-public-api.md) · [All operations](https://skmtc.dev/beel/apis/beel-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/beel/apis/beel-public-api/revisions/ffbc0ae31f79?raw)
