# Export invoices to a spreadsheet (deprecated) API Reference

Exports invoices to an XLSX file.

- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/exports`, which produces
  the same file.
- **Difference:** the selection criteria are grouped there under `filters` instead of
  being top-level fields.
- **Selection:** the invoices named in `invoice_ids`, or, when that list is absent, the
  ones matching the filters.
- **Format:** `SUMMARY` gives one row per invoice with aggregated totals, `ITEMS` one row
  per invoice line.

**Retires on 9 December 2026.** See the [migration guide](https://docs.beel.es/changelog/resources-under-the-nif) for what moved where and what changes when you switch.


## POST /v1/invoices/export/excel

**Export invoices to a spreadsheet**

Exports invoices to an XLSX file.

- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/exports`, which produces
  the same file.
- **Difference:** the selection criteria are grouped there under `filters` instead of
  being top-level fields.
- **Selection:** the invoices named in `invoice_ids`, or, when that list is absent, the
  ones matching the filters.
- **Format:** `SUMMARY` gives one row per invoice with aggregated totals, `ITEMS` one row
  per invoice line.

**Retires on 9 December 2026.** See the [migration guide](https://docs.beel.es/changelog/resources-under-the-nif) for what moved where and what changes when you switch.

**Deprecated.**

### Authentication

Accepts any of:

- `ApiKeyAuth` (HTTP bearer, token format `beel_sk_*`)

### Request Body

Required.

**Content `application/json`:**

- **invoice_ids** `array[UUID]`: List of specific invoice IDs to export. If provided, filters are ignored.
- **format** `string`: Export format. - **SUMMARY**: One row per invoice with totals (default) - **ITEMS**: One row per invoice line item — one of: SUMMARY, ITEMS
- **status** `InvoiceStatus`: - SCHEDULED: Scheduled invoice to be issued automatically on a future date - DRAFT: Draft invoice not sent yet (modifiable) - ISSUED: Finalized invoice with definitive number but not sent - SENT: Invoice sent to customer - PAID: Invoice paid - OVERDUE: Reserved. No operation sets this status and it is not computed from `due_date`; an unpaid invoice past its due date keeps its status (`ISSUED` or `SENT`). Compare `due_date` with today to find overdue invoices. - RECTIFIED: Partially corrected invoice (one or more PARTIAL corrective invoices) - VOIDED: Cancelled invoice. Reached either through a direct void request or through a TOTAL corrective invoice; `void_cause` tells the two apart. - CONVERTED: Proforma converted into an invoice (terminal; the proforma survives as the record of the accepted quote, linked to the created invoice) - ACTIVE: Active proforma. The single working state of a proforma (non-fiscal document): born numbered (PRO-...) and editable, never reaching the fiscal statuses. It transitions to CONVERTED when turned into an invoice, or to VOIDED when the offer is rejected/withdrawn (POST /v1/invoices/{invoice_id}/void). - EXPIRED: Proforma whose offer validity (`valid_until`) has passed. Derived on read and never stored; the proforma stays convertible and editable.
- **type** `InvoiceType`: - STANDARD: Standard invoice - CORRECTIVE: Corrects or cancels a previous invoice - SIMPLIFIED: Simplified invoice (ticket), for a recipient that is not identified. BeeL. requires a STANDARD invoice when the recipient is identified, at any amount: a SIMPLIFIED invoice whose recipient carries an `nif` or `alternative_id` is rejected with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`. The only amount BeeL enforces is a cap of 3,000€ VAT included (`SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`). The general limit of RD 1619/2012 is 400€ (art. 4.1.a); up to 3,000€ applies only to the activities listed in art. 4.2. BeeL does not check which activity the issuer carries out. - PROFORMA: Commercial document (formal quote) with no fiscal validity. Never enters VeriFactu (no QR, no AEAT submission): `verifactu.enabled` is always `false`, whatever the company's regime. Requires full recipient data, like STANDARD. Cannot be corrective nor reference a rectified invoice.
- **date_from** `string` (date): Issue date from (YYYY-MM-DD)
- **date_to** `string` (date): Issue date to (YYYY-MM-DD)
- **customer_id** `UUID`: Universally Unique Identifier (UUID v4)
- **recipient_name** `string`: Filter by recipient name (partial match)
- **recipient_nif** `string`: Filter by recipient NIF (partial match)
- **series_code** `string`: Filter by series code

**Example `export_by_ids`** — Export specific invoices (default format):

```json
{
  "invoice_ids": [
    "550e8400-e29b-41d4-a716-446655440001",
    "550e8400-e29b-41d4-a716-446655440002",
    "550e8400-e29b-41d4-a716-446655440003"
  ]
}
```

**Example `export_items_format`** — Export as items (one row per line):

```json
{
  "invoice_ids": [
    "550e8400-e29b-41d4-a716-446655440001"
  ],
  "format": "ITEMS"
}
```

**Example `export_q1_2025`** — Export Q1 2025 invoices:

```json
{
  "date_from": "2025-01-01",
  "date_to": "2025-03-31"
}
```

**Example `export_paid`** — Export all paid invoices:

```json
{
  "status": "PAID"
}
```

### Responses

#### 200: Excel file generated successfully

**Headers:**

- `Content-Disposition` `string`: Suggested filename for download
- `X-Total-Invoices` `integer`: Number of invoices included in the export

**Content `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`:**

Type: `string` (binary)

#### 400: `EXPORT_SELECTION_REQUIRED` — the request names no invoices: neither a non-empty `invoice_ids` nor a single filter. An export with no criteria is rejected, never answered with the whole account.

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

#### 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.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication is required to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 403: Authenticated but not allowed. Fourteen causes shared by every operation, told apart by
`error.code`; an operation that adds a cause of its own names it in its `403`. You can
branch on them exhaustively.

- `INSUFFICIENT_SCOPE` — the credential lacks a scope the operation requires;
  `error.details.missing_scopes` names them as a single comma-separated string (for
  example `"invoices:write,customers:read"`), not as an array; `required_scopes` has the
  same shape and lists every scope the operation needs. Retrying will not help: mint a key
  that holds them.
- `COMPANY_READ_ONLY` — the scope is there, but your access level over that NIF only
  lets you read it.
- `ACCOUNT_MANAGEMENT_FORBIDDEN` — the scope is there, but your role over the account,
  or the management relationship you hold over it, does not cover this operation.
- `ACCOUNT_NOT_ACCESSIBLE` — the account is not yours to reach, which is also the answer
  when it does not exist, so existence is never disclosed.
- `ACTIVE_COMPANY_NOT_ACCESSIBLE` — the same, for a NIF: the company in the path, or the
  one named by `BeeL-Active-Company`, is not one this credential may reach — and again,
  this is also the answer when it does not exist.
- `COMPANY_ACCESS_REVOKED` — your access to the NIF was withdrawn while the request was
  already in flight, so the write was rejected and nothing was recorded. Retrying will
  not help until the access is granted again.
- `LIVE_CREDENTIAL_REQUIRED` — the operation changes the real account and the call came
  from a test API key (`beel_sk_test_…`). Use your live key or the dashboard.
- `FEATURE_NOT_AVAILABLE` — your subscription does not include the entitlement the
  operation needs; `error.details.feature_code` names it. This gate runs **before** the
  scope gate, so for such an operation you never see `INSUFFICIENT_SCOPE` first.
- `NO_ACTIVE_ACCOUNT` — the credential does not resolve to an account, so no scope can be
  evaluated against one. Fail-closed, not a permission that can be granted to you.
- `OPERATION_REQUIRES_SESSION` — the operation is available only from the web session; no
  API key and no OAuth2 token can perform it, whatever scopes it holds.
- `OPERATION_REQUIRES_API_KEY` — the reverse: the operation needs a scope that only an API
  key can hold (such as `accounts:write`), so a dashboard session never satisfies it.
- `API_KEY_LIVE_NOT_AVAILABLE` — the call came with a live API key (`beel_sk_live_…`) and
  the account has no Live mode yet. It is never degraded to Test: use your test key, or
  activate Live first.
- `ACCOUNT_REQUIRES_FOCUS_SWITCH` — dashboard session only: you are a member of the account
  in the path, but it is not the account in focus. Switch focus to it and retry.
- `REQUEST_BLOCKED` — the edge security filter stopped the request before the API saw its
  body, usually because a text field carries something that looks like markup or a
  script (HTML tags, `<script>`). Review the body and retry.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "FORBIDDEN",
    "message": "You do not have permission to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 422: Validation error. `EXPORT_LIMIT_EXCEEDED` — the selection (by `invoice_ids` or by `filters`) matches more invoices than the per-export maximum. Narrow the date range or split the selection: the export is never silently truncated.

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

#### 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.


**Headers:**

- `Retry-After` `integer`: Seconds until the rate limit resets
- `RateLimit-Limit` `integer`: Maximum requests allowed in the window
- `RateLimit-Remaining` `integer`: Remaining requests in the current window
- `RateLimit-Reset` `integer`: Seconds until the current window resets

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again in 60 seconds."
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 500: Internal server error

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 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.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_MEDIA_TYPE",
    "message": "Unsupported media type: text/plain. Supported: application/json"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

---

# Related Schema Definitions

## UUID

Universally Unique Identifier (UUID v4)

Type: `string` (uuid)

## InvoiceStatus

- SCHEDULED: Scheduled invoice to be issued automatically on a future date
- DRAFT: Draft invoice not sent yet (modifiable)
- ISSUED: Finalized invoice with definitive number but not sent
- SENT: Invoice sent to customer
- PAID: Invoice paid
- OVERDUE: Reserved. No operation sets this status and it is not computed from `due_date`;
  an unpaid invoice past its due date keeps its status (`ISSUED` or `SENT`). Compare
  `due_date` with today to find overdue invoices.
- RECTIFIED: Partially corrected invoice (one or more PARTIAL corrective invoices)
- VOIDED: Cancelled invoice. Reached either through a direct void request or
  through a TOTAL corrective invoice; `void_cause` tells the two apart.
- CONVERTED: Proforma converted into an invoice (terminal; the proforma survives
  as the record of the accepted quote, linked to the created invoice)
- ACTIVE: Active proforma. The single working state of a proforma (non-fiscal
  document): born numbered (PRO-...) and editable, never reaching the fiscal
  statuses. It transitions to CONVERTED when turned into an invoice, or to VOIDED
  when the offer is rejected/withdrawn (POST /v1/invoices/{invoice_id}/void).
- EXPIRED: Proforma whose offer validity (`valid_until`) has passed. Derived on read
  and never stored; the proforma stays convertible and editable.

Type: `string` — one of: SCHEDULED, DRAFT, ISSUED, SENT, PAID, OVERDUE, RECTIFIED, VOIDED, CONVERTED, ACTIVE, EXPIRED

## InvoiceType

- STANDARD: Standard invoice
- CORRECTIVE: Corrects or cancels a previous invoice
- SIMPLIFIED: Simplified invoice (ticket), for a recipient that is not identified. BeeL.
  requires a STANDARD invoice when the recipient is identified, at any amount: a
  SIMPLIFIED invoice whose recipient carries an `nif` or `alternative_id` is rejected
  with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`. The only amount BeeL
  enforces is a cap of 3,000€ VAT included (`SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`). The
  general limit of RD 1619/2012 is 400€ (art. 4.1.a); up to 3,000€ applies only to the
  activities listed in art. 4.2. BeeL does not check which activity the issuer carries
  out.
- PROFORMA: Commercial document (formal quote) with no fiscal validity.
  Never enters VeriFactu (no QR, no AEAT submission): `verifactu.enabled` is
  always `false`, whatever the company's regime. Requires full recipient data,
  like STANDARD.
  Cannot be corrective nor reference a rectified invoice.

Type: `string` — one of: STANDARD, CORRECTIVE, SIMPLIFIED, PROFORMA

## ErrorResponse

Error response shared by all BeeL. APIs.

The payload carries **two contracts at once** (additive, non-breaking):

- **Legacy** (`success`, `error.{code,message,details}`, `meta`) — kept
  intact for existing consumers.
- **RFC 9457** (`type`, `title`, `detail`, `instance`) — new fields
  for integrators following Problem Details for HTTP APIs. The
  `type` URI is the stable, shareable link to the error's
  documentation page (e.g. `https://docs.beel.es/errors/{code}`).

Future migration: the legacy fields will be deprecated via
`Deprecation`/`Sunset` headers after a sufficient adoption window,
and the response Content-Type will move to
`application/problem+json`.

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

## ErrorDetail

- **code** (required) `string`: No description (example: "VALIDATION_ERROR")
- **message** (required) `string`: No description (example: "The provided data is not valid")
- **details** `object`: No description (example: {"field":"specific error message"})

## ResponseMeta

- **timestamp** `string` (date-time): No description (example: "2025-01-15T10:30:00Z")
- **request_id** `string`: No description (example: "4bf92f3577b34da6a3ce929d0e0e4736")


---

Full OpenAPI spec: https://docs.beel.es/api/openapi