# Update a recurring invoice (deprecated) API Reference

**Deprecated.** Use
`PATCH /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which behaves
identically.

Updates only the fields present in the body, leaving every other field of the recurring
invoice template as it is.

- **Omitted vs `null`:** an omitted field keeps its current value; a field sent as `null`
  is cleared, and only where the request schema documents the field as nullable.
- **`lines`:** replaced as a whole, not patched line by line. The recipient survives the
  change, and an empty array is rejected.
- **`payment_method`:** replaced as a whole together with `payment_iban`, `payment_swift`
  and `payment_term_days` — send them in the same request or they are dropped.
- **Schedule:** `frequency`, `day_of_month` and `start_date` stay put unless you send
  them; sending a new value for any of the three moves the next generation — resending
  the ones already in effect changes nothing. Changing `frequency` recalculates it on
  the new grid and discards a pending skip. `start_date` is only editable while the
  template has not generated any invoice yet.

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


## PATCH /v1/recurring-invoices/{recurring_invoice_id}

**Update a recurring invoice**

**Deprecated.** Use
`PATCH /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which behaves
identically.

Updates only the fields present in the body, leaving every other field of the recurring
invoice template as it is.

- **Omitted vs `null`:** an omitted field keeps its current value; a field sent as `null`
  is cleared, and only where the request schema documents the field as nullable.
- **`lines`:** replaced as a whole, not patched line by line. The recipient survives the
  change, and an empty array is rejected.
- **`payment_method`:** replaced as a whole together with `payment_iban`, `payment_swift`
  and `payment_term_days` — send them in the same request or they are dropped.
- **Schedule:** `frequency`, `day_of_month` and `start_date` stay put unless you send
  them; sending a new value for any of the three moves the next generation — resending
  the ones already in effect changes nothing. Changing `frequency` recalculates it on
  the new grid and discards a pending skip. `start_date` is only editable while the
  template has not generated any invoice yet.

**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_*`)

### Parameters

- **recurring_invoice_id** (required) in path `string`: Unique identifier (UUID) of the recurring invoice template, as returned when it is created or listed. A template of another company answers `404` with `RECURRING_NOT_FOUND`, exactly like one that does not exist.

### Request Body

Required.

**Content `application/json`:**

- **name** `string`: Template name. Cannot be cleared.
- **frequency** `string`: Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, the current one is kept — it is never reset to `MONTHLY`. **Changing it reschedules the template.** The next generation is recalculated on the new grid — the `day_of_month` dates anchored at `start_date`, one every cadence — so it can land months later than the one you saw before the call. **If the template has already issued invoices, the landing respects that**: it is never a period that was already billed, and never before today either — the first point of the new grid strictly after the last invoiced one. It also **discards a pending skip**: the skipped period only existed as a point of the old grid, and that grid is gone. Send the same cadence and nothing is rescheduled. — one of: MONTHLY, QUARTERLY, YEARLY
- **day_of_month** `integer`: Day of the month the invoices are issued. Moves the next generation immediately, on an `ACTIVE` template and on a `PAUSED` one alike: the response to this very call already carries the new `next_generation`. On a paused template the new day rules from the edit, not from the day you resume — resuming keeps the date this call left, so editing the day of a paused schedule is never ignored for a round. A month that does not have that day falls back to its last day: a template on `31` issues on 28 February (29 in a leap year) and on 30 April. So `31` is how you ask for the last day of the month it generates in — there is no separate flag for it, and the accepted range stays 1–31. The adjustment does not stick and the schedule does not drift: every generation is recalculated from the `day_of_month` you sent, never from the date it was adjusted to (31 Jan → 28 Feb → 31 Mar).
- **start_date** `string` (date): First issue date. Only **changeable** while the template has not generated any invoice yet: once it has issued, a *different* date is rejected with a 422 `RECURRING_START_DATE_NOT_EDITABLE`. Sending the value it already has is a no-op and succeeds, so a read-modify-write cycle never has to strip the field out of the body. A past date is accepted and stored as sent, but it never anchors generation in the past: `next_generation` becomes the first date of the template's own calendar on or after today (today included) — the grid of `day_of_month` dates anchored at `start_date`, one every `frequency` — which on a quarterly or yearly template can be months from now. Creating and updating apply the same rule. If today's period was already invoiced in the system you are migrating from, send a `start_date` of tomorrow (or of the next period) so it is not invoiced twice.
- **draft_in_advance** `boolean`: Whether the template prepares a draft for review before emitting. The window is fixed at 5 days and both options emit on the scheduled day. Generating manually a period whose scheduled date is today or earlier emits it on the spot. Omitted, the current value is kept.
- **preview_days** `integer`: **Deprecated.** Superseded by `draft_in_advance`. Any value greater than `0` means the same as `draft_in_advance: true`, and `0` the same as `false`. When both are sent, `draft_in_advance` wins. It will be removed in a future version.
- **end_date** `string` (date): Date the recurrence stops. Send `null` to make it open-ended.
- **max_invoices** `integer`: Total number of invoices this template will generate before ending on its own, between 2 and 600. Send `null` to stop capping the recurrence by number. Editing it MOVES THE GOAL, it does not add turns: above the invoices already generated the template stays active; exactly equal ends it in this very call (with `completion.reason = MAX_INVOICES_REACHED`); below it the call is rejected — issued invoices are fiscal facts, and someone typing a lower number is asking to stop now, which is what ending the recurrence is for. A recurrence ends in ONE way, and the conflict is judged on the RESULTING state, not on the body: sending `max_invoices` to a template that already has an `end_date` is rejected with `RECURRING_END_MODE_CONFLICT` even though the body only mentions one of them. Switching mode is a single call that says both things — the new field with a value and the old one as `null`. Nothing is cleared silently. (example: 6)
- **series_id** `string` (uuid): Series the generated invoices are numbered in. Cannot be cleared.
- **invoice_type**: Type of the invoices the template generates. Omitted, the current type is kept; it cannot be cleared. A change is judged on the resulting template, with the rules of the new type: its series must accept it (`SERIES_INCOMPATIBLE_DOC_TYPE` otherwise), a `SIMPLIFIED` template cannot keep an identified recipient (`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`) nor lines a simplified invoice does not admit. Send the new `series_id`, and `customer_id: null` when moving to `SIMPLIFIED`, in the same call.
- **customer_id** `string` (uuid): Recipient of the generated invoices. Send `null` to leave the template without a recipient; omit it to keep the current one.
- **lines** `array[RecurringLineRequest]`: Template lines, replaced as a whole (they are not patched line by line). Omit them to keep the current ones — a template with no lines invoices nothing, so an empty array is rejected.
- **payment_method**: Payment method. Replaced as a whole together with `payment_iban`, `payment_swift` and `payment_term_days`: send them in the same request or they are dropped. Send `null` to state that no payment method applies. `payment_iban`, `payment_swift` and `payment_term_days` are detail of this method, not standalone fields: sending any of them without a `payment_method` that is present, non-null and different from `NONE` is rejected with `422` (`PAYMENT_DETAILS_REQUIRE_METHOD`).
- **payment_iban** `string`: No description
- **payment_swift** `string`: No description
- **payment_term_days** `integer`: No description
- **notes** `string`: Notes printed on the generated invoices. Send `null` to clear them.
- **send_automatically** `boolean`: No description
- **email_configuration**: Email delivery settings, replaced as a whole. Send `null` to stop sending the generated invoices by email.

### Responses

#### 200: Recurring invoice updated successfully

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: true)
- **data** (required): The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
- **meta** `ResponseMeta`
- **data** `RecurringInvoiceResponse`

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

- `EXEMPTION_INCOMPATIBLE_WITH_REGIME` — the line's `exemption_reason` cannot be declared under its `regime_key` (e.g. E2/E3 with key `01`); change the key or the reason.
- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.
- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.
- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.


**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")
- **error**: No description

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "INVALID_JSON_FORMAT",
    "message": "The field 'due_date' has an invalid date format: '2026-03-04fds'. Expected format: YYYY-MM-DD.",
    "details": {
      "field": "due_date",
      "invalid_value": "2026-03-04fds",
      "expected_format": "YYYY-MM-DD"
    }
  },
  "meta": {
    "timestamp": "2026-03-05T10: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"
  }
}
```

#### 404: The resource addressed by the path does not exist, or is not one this credential can see —
the two are answered identically, so existence is never disclosed. `error.code` names the
kind of resource that was missing, so a client can tell which of several ids in a path
failed to resolve:

- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).
- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.
- `CLIENT_NOT_FOUND` — the `{customer_id}`.
- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not
  resolve to a customer of the issuing company.
- `PRODUCT_NOT_FOUND` — the `{product_id}`.
- `SERIES_NOT_FOUND` — the `{series_id}`.
- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the
  `{delivery_id}`.
- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder
  has not completed onboarding, so there is no issuer to act as.
- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.

Some resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,
`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it
is documented on the operation.

A `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist
in this API (a typo in the route, or a resource that was never here). It says nothing about
any resource. When the path exists but not with that method, the answer is `405`, not `404`.

Other `error.code` values with this status:

- `INVOICE_SERIES_NOT_FOUND` — the `series_id` does not exist in this company and environment (a Test series is invisible from Live and vice versa). List the series and send an existing id, or omit it to use the default.


**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": "INVOICE_NOT_FOUND",
    "message": "Invoice not found"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 409: The request is well formed but collides with the state that is already there. Three
families, told apart by `error.code`:

- **Uniqueness** — something with that identity already exists (`CLIENT_DUPLICATE`,
  `PRODUCT_DUPLICATE`, `SERIES_CODE_DUPLICATED`, `NIF_ALREADY_REGISTERED`,
  `MEMBER_ALREADY_IN_ACCOUNT`, `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`).
- **Lifecycle** — the transition does not apply from where the resource is
  (`INVOICE_ALREADY_VOIDED`, `PROFORMA_ALREADY_CONVERTED`, `CLIENT_HAS_INVOICES`,
  `RECURRING_ALREADY_ENDED`, `RECURRING_OCCURRENCE_ALREADY_CONSUMED` — the occurrence the
  recurring template points at has already been billed; read its history instead of
  retrying).
  Read the resource before retrying: the state you assumed is not its state.
- **Concurrency and idempotency** — another write got there first
  (`CONCURRENT_MODIFICATION`), or the `Idempotency-Key` you sent is still in flight
  (`IDEMPOTENCY_KEY_PROCESSING`, retry after a moment) or was already used for a
  different body (`IDEMPOTENCY_KEY_MISMATCH`, use a new key).

`CONFLICT` is the generic fallback of the uniqueness family, when the uniqueness that was
violated has no code of its own.


**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": "CONCURRENT_MODIFICATION",
    "message": "The resource was modified by another request; read it again and retry"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 422: Validation error.
The update is judged on the resulting template, not only on the fields the request
touches: if a line ends up with an `exemption_reason` that requires the recipient to
carry a specific alternate identifier type (today, `EXENTA_ART_25` needs `NIF_IVA`) —
whether `customer_id` changed in this same request or the line did — and the customer
does not carry it, the update is rejected with `error.code`
`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` and nothing is written; the template keeps the
customer and lines it had.
Separately: on a line priced by declared total (`total_excluding_tax` or
`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —
so a quotient that does not fit the field storing it is rejected with `error.code`
`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows
`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a
`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and
the quotient as JSON numbers, so they can be compared without parsing `error.message`.
A `unit_price` you send with more than 4 decimal places is rejected with `error.code`
`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.
Also: changing `frequency` is checked against the window. If the new cadence leaves
no occurrence at all between `start_date` and `end_date`, the update is rejected with
`error.code` `RECURRING_NO_OCCURRENCE_IN_WINDOW`, whose `error.details` hangs off
`end_date` — the field you can move; the other lever is `frequency` itself. Nothing is
written, so the template keeps the cadence and the `next_generation` it had. Shortening
`end_date` without touching `frequency` is a different case and not this check: that
still answers `RECURRING_END_DATE_BEFORE_START` when the end falls before the next
generation.
Also: the update is judged on the resulting template, not only on the fields the
request touches. If the template's series ends up inactive — its current one, left
untouched, or one sent in this same request — the update is rejected with `error.code`
`SERIES_INACTIVE` and nothing is written. Activate the series, or send a `series_id`
for one that is already active.

Other `error.code` values with this status:

- `EXEMPTION_NOT_FOR_ISSUED_INVOICE` — `EXENTA_ART_26` exempts the buyer's intra-community acquisition, not a sale you invoice; use `EXENTA_ART_25` for the intra-community supply.
- `EXEMPT_ZERO_RATE_REQUIRES_REASON` — a 0 % IVA or IPSI line is the exemption sentinel and needs an `exemption_reason`; add one or use a real rate.
- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.
- `INVALID_IVA_SURCHARGE_PAIR` — the equivalence surcharge does not match the line's VAT rate (21↔5.2, 10↔1.4, 5↔0.62, 4↔0.5); fix either rate.
- `INVALID_PERCENTAGE` — the `percentage` is not a rate of that tax type (e.g. IVA 13); the message lists the valid rates. OSS key `17` accepts any EU rate.
- `INVALID_REGIME_KEY_FOR_TAX_TYPE` — the `regime_key` does not exist for that tax type (e.g. `18` under IGIC); `details.allowed_values` lists the accepted keys.
- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.
- `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER` — the issuer is a company (corporate income tax), which can only withhold 0, 19 or 24 %; use one of those.
- `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER` — the rate (e.g. 9.5 % Ceuta/Melilla) only applies to corporate issuers; an individual issuer must use a personal-income-tax rate.
- `LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT` — a line with `total_excluding_tax` or `total_including_tax` cannot also send `discount_percentage`; the total already includes any discount.
- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.
- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.
- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.
- `LINE_UNIT_PRICE_XOR_DECLARED_TOTAL` — each line must carry exactly one of `unit_price`, `total_excluding_tax` or `total_including_tax`; none or several were sent.
- `NEGATIVE_TOTAL_REQUIRES_RECTIFICATIVE` — the lines add up to a negative total; only a corrective invoice can be negative. Correct the original with `POST …/corrective`.
- `PAYMENT_DETAILS_REQUIRE_METHOD` — `payment_iban`, `payment_swift` or `payment_term_days` was sent without a `payment_method` (or with `NONE`); send the method with them.
- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.
- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.
- `RECURRING_END_MODE_CONFLICT` — the resulting template would have both `end_date` and `max_invoices`. A recurrence ends one way: send the new field with a value and the other as `null` in the same call.
- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.
- `RECURRING_MAX_INVOICES_BELOW_GENERATED` — the new `max_invoices` is lower than the invoices this template already generated. Issued invoices cannot be taken back: send a cap equal or higher.
- `RECURRING_MAX_INVOICES_BELOW_MINIMUM` — `max_invoices` is below 2. For a single future invoice, schedule it instead with `PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`.
- `RECURRING_START_DATE_NOT_EDITABLE` — the template already generated invoices, so `start_date` cannot change. Resend the current value or leave it out.
- `REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED` — AEAT does not accept that `exemption_reason` (or its absence) with the line's `regime_key`; the message lists the reasons it admits.
- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.
- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.
- `REGIME_KEY_REQUIRES_STANDARD_INVOICE` — the line's `regime_key` is only accepted on a standard (F1) invoice; change the invoice type or the key.
- `REGIME_KEY_REQUIRES_VAT_RATE` — the line's `regime_key` only admits one VAT rate (named in the message); use that rate or an exemption reason.
- `REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION` — the line's `regime_key` requires one of a set of exemption reasons (e.g. `02` exports) and the line has none or another.
- `REVERSE_CHARGE_CASE_NOT_SUPPORTED` — that reverse-charge case (e.g. `ISP_ART_84_2_G`, which needs a special series) is not supported; issue it outside BeeL. or use another reason.
- `SIMPLIFICADA_FORBIDS_CROSS_BORDER` — a simplified invoice cannot document a cross-border operation (art. 25 exemption or not subject by place of supply); issue a standard invoice.
- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.
- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.
- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.
- `SURCHARGE_REQUIRES_REGIME` — an equivalence surcharge was sent with a `regime_key` other than `01`/`18` (e.g. `02`, `03`, `17`); only regime `18` admits it.


**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": "VALIDATION_ERROR",
    "message": "Validation error",
    "details": {
      "field_name": "Field is required"
    }
  },
  "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

## PatchRecurringInvoiceRequest

Partial update of a recurring invoice (RFC 5789). Only the fields present in the
body are touched:

- **field omitted** → the current value is kept;
- **field sent with a value** → replaced;
- **field sent as `null`** → cleared (only where documented as nullable below).

In particular, changing the lines no longer wipes the recipient, and the schedule
(`day_of_month`, `start_date`) stays put unless you send it.

- **name** `string`: Template name. Cannot be cleared.
- **frequency** `string`: Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, the current one is kept — it is never reset to `MONTHLY`. **Changing it reschedules the template.** The next generation is recalculated on the new grid — the `day_of_month` dates anchored at `start_date`, one every cadence — so it can land months later than the one you saw before the call. **If the template has already issued invoices, the landing respects that**: it is never a period that was already billed, and never before today either — the first point of the new grid strictly after the last invoiced one. It also **discards a pending skip**: the skipped period only existed as a point of the old grid, and that grid is gone. Send the same cadence and nothing is rescheduled. — one of: MONTHLY, QUARTERLY, YEARLY
- **day_of_month** `integer`: Day of the month the invoices are issued. Moves the next generation immediately, on an `ACTIVE` template and on a `PAUSED` one alike: the response to this very call already carries the new `next_generation`. On a paused template the new day rules from the edit, not from the day you resume — resuming keeps the date this call left, so editing the day of a paused schedule is never ignored for a round. A month that does not have that day falls back to its last day: a template on `31` issues on 28 February (29 in a leap year) and on 30 April. So `31` is how you ask for the last day of the month it generates in — there is no separate flag for it, and the accepted range stays 1–31. The adjustment does not stick and the schedule does not drift: every generation is recalculated from the `day_of_month` you sent, never from the date it was adjusted to (31 Jan → 28 Feb → 31 Mar).
- **start_date** `string` (date): First issue date. Only **changeable** while the template has not generated any invoice yet: once it has issued, a *different* date is rejected with a 422 `RECURRING_START_DATE_NOT_EDITABLE`. Sending the value it already has is a no-op and succeeds, so a read-modify-write cycle never has to strip the field out of the body. A past date is accepted and stored as sent, but it never anchors generation in the past: `next_generation` becomes the first date of the template's own calendar on or after today (today included) — the grid of `day_of_month` dates anchored at `start_date`, one every `frequency` — which on a quarterly or yearly template can be months from now. Creating and updating apply the same rule. If today's period was already invoiced in the system you are migrating from, send a `start_date` of tomorrow (or of the next period) so it is not invoiced twice.
- **draft_in_advance** `boolean`: Whether the template prepares a draft for review before emitting. The window is fixed at 5 days and both options emit on the scheduled day. Generating manually a period whose scheduled date is today or earlier emits it on the spot. Omitted, the current value is kept.
- **preview_days** `integer`: **Deprecated.** Superseded by `draft_in_advance`. Any value greater than `0` means the same as `draft_in_advance: true`, and `0` the same as `false`. When both are sent, `draft_in_advance` wins. It will be removed in a future version.
- **end_date** `string` (date): Date the recurrence stops. Send `null` to make it open-ended.
- **max_invoices** `integer`: Total number of invoices this template will generate before ending on its own, between 2 and 600. Send `null` to stop capping the recurrence by number. Editing it MOVES THE GOAL, it does not add turns: above the invoices already generated the template stays active; exactly equal ends it in this very call (with `completion.reason = MAX_INVOICES_REACHED`); below it the call is rejected — issued invoices are fiscal facts, and someone typing a lower number is asking to stop now, which is what ending the recurrence is for. A recurrence ends in ONE way, and the conflict is judged on the RESULTING state, not on the body: sending `max_invoices` to a template that already has an `end_date` is rejected with `RECURRING_END_MODE_CONFLICT` even though the body only mentions one of them. Switching mode is a single call that says both things — the new field with a value and the old one as `null`. Nothing is cleared silently. (example: 6)
- **series_id** `string` (uuid): Series the generated invoices are numbered in. Cannot be cleared.
- **invoice_type**: Type of the invoices the template generates. Omitted, the current type is kept; it cannot be cleared. A change is judged on the resulting template, with the rules of the new type: its series must accept it (`SERIES_INCOMPATIBLE_DOC_TYPE` otherwise), a `SIMPLIFIED` template cannot keep an identified recipient (`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`) nor lines a simplified invoice does not admit. Send the new `series_id`, and `customer_id: null` when moving to `SIMPLIFIED`, in the same call.
- **customer_id** `string` (uuid): Recipient of the generated invoices. Send `null` to leave the template without a recipient; omit it to keep the current one.
- **lines** `array[RecurringLineRequest]`: Template lines, replaced as a whole (they are not patched line by line). Omit them to keep the current ones — a template with no lines invoices nothing, so an empty array is rejected.
- **payment_method**: Payment method. Replaced as a whole together with `payment_iban`, `payment_swift` and `payment_term_days`: send them in the same request or they are dropped. Send `null` to state that no payment method applies. `payment_iban`, `payment_swift` and `payment_term_days` are detail of this method, not standalone fields: sending any of them without a `payment_method` that is present, non-null and different from `NONE` is rejected with `422` (`PAYMENT_DETAILS_REQUIRE_METHOD`).
- **payment_iban** `string`: No description
- **payment_swift** `string`: No description
- **payment_term_days** `integer`: No description
- **notes** `string`: Notes printed on the generated invoices. Send `null` to clear them.
- **send_automatically** `boolean`: No description
- **email_configuration**: Email delivery settings, replaced as a whole. Send `null` to stop sending the generated invoices by email.

## RecurringLineRequest

Recurring-invoice line. Unlike invoice lines (which nest tax data under a `main_tax` object),
recurring lines use flat tax fields: `vat_rate`, `tax_type`, `regime_key`,
`equivalence_surcharge_rate` and `irpf_rate`. Do not send a `main_tax` object here.

**Line amount**: send **exactly one** of `unit_price`, `total_excluding_tax` or
`total_including_tax` — same contract as an invoice line. Sending none, or more than
one, is rejected with `LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`; a declared total together
with an explicit `discount_percentage` is rejected with
`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT` (the discount, if any, already lives inside
the total).

The mode is what the template promises, and it is re-applied on **every** generation:
a line declared as `total_including_tax: 1.00` over 300 units invoices 1,00 € on every
generation, not 300 × 0,0033 = 0,99 €. Because `PATCH` replaces the lines as a whole,
resending a line with a different amount field switches its mode.

- **description** (required) `string`: No description
- **quantity** (required) `number`: No description
- **unit** `string`: No description
- **unit_price** `number`: Unit price before taxes. Supports up to 4 decimal places for micro-pricing (e.g. €0.0897/unit for labels, packaging); the generated invoices always round their amounts to 2 decimals. Mutually exclusive with `total_excluding_tax` and `total_including_tax`. The upper bound is the same one the invoice line declares, and so is the acceptance of `0` (a discount granted before or simultaneously with the sale, e.g. a free introductory month). A price outside the range is rejected with `422 VALIDATION_ERROR` and the offending field in `details`. Negative prices are NOT accepted, unlike an invoice line of a corrective invoice: a recurring template only issues `STANDARD` or `SIMPLIFIED` invoices, and a corrective is created through its own endpoint over an already issued invoice — never generated by a template. (example: 50)
- **total_excluding_tax** `number`: Declared line total excluding taxes: this amount IS the taxable base, exactly, with no recalculation. The stored `unit_price` becomes derived and informational (`total / quantity`, 4 decimals). Mutually exclusive with `unit_price` and `total_including_tax`. Negative totals are NOT accepted, for the same reason `unit_price` does not accept negative prices: a template only issues `STANDARD` or `SIMPLIFIED` invoices, and only a corrective invoice — created through its own endpoint over an already issued invoice — carries a negative amount. A total outside the range is rejected with `422 VALIDATION_ERROR` and the offending field in `details`. The upper bound is the same one `POST /v1/invoices` declares for a line total: a template is a promise to issue an invoice, and it cannot accept less than the invoice it will generate does. The derived `unit_price` (`total / quantity`) can still exceed the `maximum` this contract accepts for `unit_price` itself — what actually bounds it then is the domain's price ceiling, not this field's contract. (example: 1)
- **total_including_tax** `number`: Declared line total including taxes — what the customer pays. The engine works the breakdown backwards so that taxable base + VAT + equivalence surcharge equals this amount exactly on every generated invoice. IRPF withholding is never part of the decomposition: it is a retention, not a price. Mutually exclusive with `unit_price` and `total_excluding_tax`. Negative totals are NOT accepted, same as `total_excluding_tax`. The upper bound is the same one `POST /v1/invoices` declares for a line total, for the same reason: the template cannot promise more than the invoice it generates could ever accept. The derived `unit_price` (`total / quantity`) can still exceed the `maximum` this contract accepts for `unit_price` itself — what actually bounds it then is the domain's price ceiling, not this field's contract. (example: 100)
- **discount_percentage** `number`: No description
- **tax_type** `string`: Tax type. Omitted, `IVA` applies.
- **vat_rate** (required) `number`: No description
- **regime_key** `string`: VeriFactu regime key. Omitted, `01` (general regime) applies.
- **equivalence_surcharge_rate** `number`: No description
- **irpf_rate** `number`: No description
- **exemption_reason** `ExemptionReason`: Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the VeriFactu code each one is reported as. - `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational, cultural and financial services, or housing rentals). E1. - `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2. - `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3. - `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4. - `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5. - `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is `EXENTA_ART_25`. - `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as a going concern, art. 7.1º). N1. - `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community or non-EU services, arts. 69 and 70). N2. - `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought or semi-finished gold), c) (scrap, waste and recovery materials, plastic, paper, cardboard, glass and textile waste, and semi-finished non-ferrous metal products), d) (greenhouse gas emission allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption waived, or enforcing a security) and f) (construction or renovation works). S2. - `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones, consoles, laptops and tablets). The law requires these supplies to be invoiced in a special series, so an invoice line that carries it is rejected with `REVERSE_CHARGE_CASE_NOT_SUPPORTED`. - `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key` `04`). E6. - `REGIMEN_ART_129` (agriculture, livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods, art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163 sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime key rather than by an exemption code. An invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`; declare the regime with `regime_key` instead. - `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.
- **exemption_reason_text** `string`: Custom exemption text. Only used when `exemption_reason` is `OTRO`. Same shape as an invoice line: the template declares WHY the operation carries no tax, and every invoice it generates inherits it. A 0% line without a reason is rejected on write — VeriFactu does not accept an exempt line with no explicit motive.

## SuccessResponse

The envelope every successful JSON response of the BeeL. API is wrapped in. There are no
bare resources in v1 and none are planned: the payload always hangs off `data`.

- A **single resource** is an object in `data`.
- A **collection** hangs off a named key inside `data`, together with its `pagination`,
  also inside `data` — `data: {invoices: [...], pagination: {...}}`.
- A collection carries `pagination` unless its operation declares itself a **closed
  catalogue**: a fixed, bounded list with nothing to page through. The declaration is
  explicit in the operation; a missing `pagination` is never something to infer.
- `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a
  documented variant of pagination, not another envelope.

Putting the array straight into `data` with `pagination` as its sibling is the shape a v2
would adopt; v1 is not being flipped to it.

- **success** (required) `boolean`: No description (example: true)
- **data** (required): The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
- **meta** `ResponseMeta`

## ResponseMeta

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

## RecurringInvoiceResponse

- **id** `string` (uuid): No description
- **name** `string`: No description
- **frequency** `string`: Billing cadence of the template: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. The day it falls on is `day_of_month`, the same anchor for all three. Set it when you create the template; a template created without it is `MONTHLY`. The cadence governs the step from the first invoice onwards, not where that first one lands: the first occurrence is the first `day_of_month` on or after `start_date`, found one month at a time. A yearly template starting 15 February with `day_of_month` 10 first invoices on 10 March, then every 10 March after that. — one of: MONTHLY, QUARTERLY, YEARLY
- **day_of_month** `integer`: Day of the month the invoices are issued, as it was requested: this is the anchor of the schedule, not the day the last invoice happened to land on. A month that does not have that day falls back to its last day, so `next_generation` can show a different day than this one — a template on `31` generates on 28 February (29 in a leap year) and on 30 April. The adjustment never sticks and the schedule does not drift: every generation is recalculated from this value, never from the date it was adjusted to (31 Jan → 28 Feb → 31 Mar).
- **start_date** `string` (date): No description
- **end_date** `string` (date): No description
- **next_generation** `string` (date): Date the next invoice is generated — the one this template is actually going to honour. `null`, always present and never omitted, when there is none. There is none in two cases, and only in those two: - the template is `COMPLETED`: the date left on it is the one that ran past its end and triggered the closure, not a generation still to come; - the template is `PAUSED` and its date is strictly in the past: resuming does not go back to it — it reschedules from today onwards, so that date will never be invoiced. An `ACTIVE` template always carries its date, **including a date already in the past**: that is the occurrence about to be generated, and it is your only notice that an invoice is on its way. A `PAUSED` template whose date is still ahead — today counts as ahead — carries it too: it is exactly the date resuming would keep. The stored schedule is never rewritten by any of this. `next_generation` is what gets published, not what gets kept: resuming a long-paused template still reschedules from its own anchor, as it always did.
- **draft_in_advance** `boolean`: Whether this template prepares a draft for review before emitting. The review window is fixed at 5 days: `true` creates the draft 5 days ahead, `false` goes straight to the emission. Either way the invoice is emitted on the scheduled `day_of_month` — what changes is only whether there is a draft to look at first. Generating manually (`POST .../generate`) a period whose scheduled date is today or earlier emits the invoice on the spot: its review window has already passed. (example: true)
- **status**: No description
- **is_failing** `boolean`: Whether this template is failing right now — from the FIRST failed unattended run, not the third. The warning email only goes out on every 3rd consecutive retryable failure, so `next_generation` not moving used to be the only clue before that; this field surfaces the same signal from the very first one. `true` when either an unattended run has failed at least once and the retry streak has not been cleared by a successful generation yet (even while `status` is still `ACTIVE`, before any email fires), or `status` is `PAUSED` with `pause.reason = GENERATION_FAILURE`. Derived on every read from data already stored — it is not a new column and nothing else changes because of it. (example: false)
- **pause** `RecurringInvoicePause`: Why this schedule is paused. Present while `status` is `PAUSED`; absent otherwise, and also absent for schedules paused before this was recorded. `PAUSED` covers three facts that are not interchangeable: someone paused it, a plan downgrade paused it, or an unattended generation failed with something waiting will not fix. A transient failure (network, timeout, tax authority downtime) does NOT pause the schedule — the next run retries it.
- **completion** `RecurringInvoiceCompletion`: Why this schedule ended. Present while `status` is `COMPLETED`; absent otherwise, and also absent for schedules completed before this was recorded — that cause is not known and is not invented. `COMPLETED` covers facts that are not interchangeable: the owner ended it, the calendar ran out, or the agreed number of invoices did. It stays a **single** terminal status and `status` keeps its three values: no business rule branches on the cause, and a new status value would break every exhaustive `switch`. New causes land in `reason`, so treat it as an open set.
- **series_id** `string` (uuid): No description
- **series_code** `string`: No description
- **invoice_type** `RecurringInvoiceType`: Type of the invoices a recurring template generates: `STANDARD` for an identified recipient, `SIMPLIFIED` for a recipient that is not identified.
- **customer_id** `string` (uuid): No description
- **recipient_fiscal_name** `string`: No description
- **recipient_nif** `string`: Spanish Tax ID of the recipient. Mutually exclusive with `recipient_alternative_id`: a recipient is identified by exactly one of the two, never both, and `null` when the other one carries the identifier (or when there is no recipient identified at all).
- **recipient_alternative_id**: No description
- **lines** `array[InvoiceLineTemplateResponse]`: No description
- **amount** `number`: What the customer will be asked to transfer on the next invoice this template generates: taxable base + VAT + equivalence surcharge − IRPF withholding + disbursements (`suplidos`). It is NOT the taxable base and NOT the total with VAT: the withholding is already subtracted. The same number the next occurrence publishes as `total_to_pay`, to the cent: both come from the template's own lines through the same line pipeline the real generation uses. It is computed on the fly and stored nowhere, so it always reflects the lines the template carries right now — and it is not sortable or filterable for that reason. `null` when the template's lines cannot be priced (no lines, or lines the engine rejects). The rest of the template is still returned. (example: 106)
- **payment_method** `string`: No description
- **payment_iban** `string`: Full IBAN of the template's own payment method, mirrored from what was sent when it was set. `null` when the template has no payment method.
- **payment_swift** `string`: SWIFT/BIC code of the template's own payment method. `null` when the template has no payment method, or when it has one without a SWIFT code.
- **payment_term_days** `integer`: Payment term, in days, of the template's own payment method. `null` when the template has no payment method, or when it has one without a declared term. Each generated invoice is due on its issue date plus this term. When it is `null`, the company's default `payment_term_days` (tax configuration) applies, or 30 days if the company has none.
- **notes** `string`: No description
- **send_automatically** `boolean`: No description
- **email_configuration** `RecurringEmailConfigResponse`
- **generated_invoices** `integer`: No description
- **max_invoices** `integer`: Total number of invoices agreed for this template, or absent when the recurrence is not capped by number. It is the TOTAL agreed, not what is left: "remaining" is `max_invoices - generated_invoices`, a derived reading with no field of its own — two counters of the same fact drift, and the day they do nobody knows which one is true. What counts against it are invoices GENERATED, not calendar turns: a skip does not spend cap because it produces no invoice, and a `generate now` does because it produces a real fiscal invoice. When the last one is generated the template is closed in the act, with `completion.reason = MAX_INVOICES_REACHED`. (example: 6)
- **last_generated_at** `string` (date-time): No description
- **last_generated_scheduled_date** `string` (date): Calendar date of the latest period this template has already invoiced: the highest `scheduled_date` in its generation history. `null` when it has never generated an invoice. It is NOT the date of `last_generated_at`. That field is the instant the most recent invoice was created; this one is the calendar slot that invoice (or an earlier one) was issued for. They diverge on purpose: a `generate now` creates today an invoice for a future slot, and a re-anchored schedule can generate a slot out of order, so the latest invoice created is not necessarily the one with the latest slot. Skipping a period does not move it: a skip produces no invoice, so it consumes no slot. Deleting a generated invoice does not move it either, with one exception: deleting the draft of the latest generation releases that slot (the period becomes available to generate again, and `generated_invoices` goes down by one), so this date moves back to the previous generated slot, or to `null` if there is none. (example: "2026-05-01")
- **source_invoice_id** `string` (uuid): No description
- **created_at** `string` (date-time): No description
- **updated_at** `string` (date-time): No description

## 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"})

## ExemptionReason

Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the
VeriFactu code each one is reported as.

- `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational,
  cultural and financial services, or housing rentals). E1.
- `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2.
- `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3.
- `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4.
- `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5.
- `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the
  buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it
  is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is
  `EXENTA_ART_25`.
- `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as
  a going concern, art. 7.1º). N1.
- `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community
  or non-EU services, arts. 69 and 70). N2.
- `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del
  sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought
  or semi-finished gold), c) (scrap, waste and recovery materials, plastic, paper, cardboard, glass and textile waste, and semi-finished non-ferrous metal products), d) (greenhouse gas emission
  allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption
  waived, or enforcing a security) and f) (construction or renovation works). S2.
- `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones,
  consoles, laptops and tablets). The law requires these supplies to be invoiced in a special
  series, so an invoice line that carries it is rejected with
  `REVERSE_CHARGE_CASE_NOT_SUPPORTED`.
- `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key`
  `04`). E6.
- `REGIMEN_ART_129` (agriculture,
  livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods,
  art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence
  surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163
  sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime
  key rather than by an exemption code. An
  invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`;
  declare the regime with `regime_key` instead.
- `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.

Type: `string` — one of: EXENTA_ART_20, EXENTA_ART_21, EXENTA_ART_22, EXENTA_ART_24, EXENTA_ART_25, EXENTA_ART_26, EXENTA_ART_140, NO_SUJETA_ART_7_9, NO_SUJETA_LOCALIZACION, ISP_ART_84_2_A, ISP_ART_84_2_B, ISP_ART_84_2_C, ISP_ART_84_2_D, ISP_ART_84_2_E, ISP_ART_84_2_F, ISP_ART_84_2_G, REGIMEN_ART_129, REGIMEN_ART_135, REGIMEN_ART_141, REGIMEN_ART_154, REGIMEN_ART_163_DECIES, OTRO

## RecurringInvoicePause

Why this schedule is paused. Present while `status` is `PAUSED`; absent otherwise, and also
absent for schedules paused before this was recorded.

`PAUSED` covers three facts that are not interchangeable: someone paused it, a plan downgrade
paused it, or an unattended generation failed with something waiting will not fix. A transient
failure (network, timeout, tax authority downtime) does NOT pause the schedule — the next run
retries it.

- **reason** `RecurringInvoicePauseReason`: Who stopped the schedule. `USER` a person did, from the API or the dashboard; `DOWNGRADE` the account lost the recurring-invoices feature; `GENERATION_FAILURE` an unattended run failed with something waiting will not fix — that one carries a `blocker`.
- **since** `string` (date-time): No description
- **blocker** `string`: What blocked the emission, using the same codes the emission error returns (the `blockers[]` of a 422 `EMISSION_NOT_READY`). Only for `GENERATION_FAILURE`, and it may be absent when the permanent failure was not a readiness one. Resuming is rejected while this blocker is still in effect. (example: "NIF_NOT_REGISTERED")

## RecurringInvoiceCompletion

Why this schedule ended. Present while `status` is `COMPLETED`; absent otherwise, and also
absent for schedules completed before this was recorded — that cause is not known and is not
invented.

`COMPLETED` covers facts that are not interchangeable: the owner ended it, the calendar ran
out, or the agreed number of invoices did. It stays a **single** terminal status and `status` keeps its three values: no business rule
branches on the cause, and a new status value would break every exhaustive `switch`. New causes
land in `reason`, so treat it as an open set.

- **reason** `string`: `USER` — someone completed it (`PUT .../status` to `COMPLETED`). `END_DATE_REACHED` — the next occurrence landed past `end_date`, so there was nothing left to generate. This includes automatic generation and the common close of generating, skipping and resuming. `MAX_INVOICES_REACHED` — the `max_invoices` cap ran out: the last agreed invoice was generated (or the cap was moved down to the number already generated). When a template exhausts its cap and its `end_date` on the very same turn this is the cause reported: the cap is the fact that just happened, the date is the calendar consequence that follows. — one of: USER, END_DATE_REACHED, MAX_INVOICES_REACHED
- **at** `string` (date-time): Instant the schedule was closed.

## RecurringInvoiceType

Type of the invoices a recurring template generates: `STANDARD` for an identified recipient,
`SIMPLIFIED` for a recipient that is not identified.

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

## InvoiceLineTemplateResponse

- **id** `string` (uuid): No description
- **order** `integer`: No description
- **description** `string`: No description
- **quantity** `number`: No description
- **unit** `string`: No description
- **unit_price** `number`: Unit price of the template line. In both total-declared modes (`pricing_mode` other than `UNIT_PRICE`) this value is **derived and informational** (`total / quantity`, 4 decimals): re-multiplying it does not reproduce the declared total on large quantities.
- **pricing_mode** `PricingMode`: How the line amount was entered. - `UNIT_PRICE`: classic mode — the amount is derived from `unit_price` (`quantity × unit_price × (1 − discount / 100)`). - `TOTAL_EXCLUDING_TAX`: total-declared mode — `total_excluding_tax` is the exact taxable base and `unit_price` is derived and informational (`total / quantity`, 4 decimals). - `TOTAL_INCLUDING_TAX`: tax-inclusive total-declared mode — `total_including_tax` is what the customer paid (taxable base + VAT + equivalence surcharge) and the engine works the breakdown backwards so the rounded amounts add up to the declared total exactly. In both total-declared modes re-multiplying the derived `unit_price` does NOT reproduce the declared total on large quantities (1.00 € ÷ 300 units → 0.0033 × 300 = 0.99), which is precisely why the declared total is the source of truth.
- **total_excluding_tax** `number`: Declared line total excluding taxes. Only present on lines with `pricing_mode = TOTAL_EXCLUDING_TAX`. It never includes taxes nor subtracts IRPF withholding. (example: 1)
- **total_including_tax** `number`: Declared line total including taxes (taxable base + VAT + equivalence surcharge; IRPF withholding is never subtracted). Only present on lines with `pricing_mode = TOTAL_INCLUDING_TAX`. Every invoice this template generates reproduces it exactly. (example: 100)
- **discount_percentage** `number`: No description
- **tax_type** `string`: No description
- **vat_rate** `number`: No description
- **regime_key** `string`: No description
- **equivalence_surcharge_rate** `number`: No description
- **irpf_rate** `number`: No description
- **exemption_reason** `ExemptionReason`: Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the VeriFactu code each one is reported as. - `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational, cultural and financial services, or housing rentals). E1. - `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2. - `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3. - `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4. - `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5. - `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is `EXENTA_ART_25`. - `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as a going concern, art. 7.1º). N1. - `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community or non-EU services, arts. 69 and 70). N2. - `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought or semi-finished gold), c) (scrap, waste and recovery materials, plastic, paper, cardboard, glass and textile waste, and semi-finished non-ferrous metal products), d) (greenhouse gas emission allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption waived, or enforcing a security) and f) (construction or renovation works). S2. - `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones, consoles, laptops and tablets). The law requires these supplies to be invoiced in a special series, so an invoice line that carries it is rejected with `REVERSE_CHARGE_CASE_NOT_SUPPORTED`. - `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key` `04`). E6. - `REGIMEN_ART_129` (agriculture, livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods, art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163 sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime key rather than by an exemption code. An invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`; declare the regime with `regime_key` instead. - `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.
- **exemption_reason_text** `string`: Custom exemption text. Only used when exemption_reason is OTRO.

## RecurringEmailConfigResponse

- **recipients** `array[string]`: No description
- **cc** `array[string]`: No description
- **subject** `string`: No description
- **message** `string`: No description

## RecurringInvoicePauseReason

Who stopped the schedule. `USER` a person did, from the API or the dashboard; `DOWNGRADE` the
account lost the recurring-invoices feature; `GENERATION_FAILURE` an unattended run failed with
something waiting will not fix — that one carries a `blocker`.

Type: `string` — one of: USER, DOWNGRADE, GENERATION_FAILURE

## PricingMode

How the line amount was entered.

- `UNIT_PRICE`: classic mode — the amount is derived from `unit_price`
  (`quantity × unit_price × (1 − discount / 100)`).
- `TOTAL_EXCLUDING_TAX`: total-declared mode — `total_excluding_tax` is the exact
  taxable base and `unit_price` is derived and informational
  (`total / quantity`, 4 decimals).
- `TOTAL_INCLUDING_TAX`: tax-inclusive total-declared mode —
  `total_including_tax` is what the customer paid (taxable base + VAT +
  equivalence surcharge) and the engine works the breakdown backwards so the
  rounded amounts add up to the declared total exactly.

In both total-declared modes re-multiplying the derived `unit_price` does NOT
reproduce the declared total on large quantities (1.00 € ÷ 300 units → 0.0033 ×
300 = 0.99), which is precisely why the declared total is the source of truth.

Type: `string` — one of: UNIT_PRICE, TOTAL_EXCLUDING_TAX, TOTAL_INCLUDING_TAX


---

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