---
title: "Update invoice series partially"
method: PATCH
path: "/v1/configuration/series/{series_id}"
tags: ["InvoiceSeries"]
deprecated: true
---

# Update invoice series partially

`PATCH /v1/configuration/series/{series_id}`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/configuration/series/{series_id}`
- Auth: HTTP bearer

> **Deprecated.**

**Deprecated.** Use `PATCH /v1/companies/{company_id}/series/{series_id}`, which behaves
identically.

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

- **Clearing a field:** a field sent as `null` is cleared — only `description` supports it
  (see `PatchSeriesRequest`).
- **Numbering fields:** the same guard as `PUT`. `code`, `format`, `counter_reset` and
  `initial_number` are rejected once the series has issued invoices.
- **`default_series`:** it is not a way to clear the default. Sending `false` for the
  series that currently *is* the default is rejected with `DEFAULT_CANNOT_BE_UNMARKED`;
  promote another series with
  `PUT /v1/companies/{company_id}/series/{series_id}/default` instead. Sending `false` for
  a series that is *not* the default stays a no-op `200`.

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

## Path parameters

- `series_id` string, uuid, required: Universally Unique Identifier (UUID v4)

## Request body

- PatchSeriesRequest: Partial update of an invoice series (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 `description`, the one field a series can live without). The same rules as `PUT` apply: the fields that drive numbering (`code`, `format`, `counter_reset`, `initial_number`) and `document_type` are rejected once the series has issued invoices (`document_type` can still be set on an `UNASSIGNED` series), so renaming a series in production keeps working. Moving a series to `document_type: UNASSIGNED` is rejected with `422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`; a series that already is `UNASSIGNED` can be edited without changing its type.
  - `document_type` 'UNASSIGNED' | 'STANDARD' | 'SIMPLIFIED' | 'CORRECTIVE' | 'SIMPLIFIED_CORRECTIVE' | 'PROFORMA': Document type associated with a series. Values mirror `InvoiceType`, so the series a document needs is named exactly like the document: - UNASSIGNED: Legacy value of series created before types existed. A series numbers only documents of its own type, so an `UNASSIGNED` series numbers none (`422 SERIES_INCOMPATIBLE_DOC_TYPE`); give it a type to keep using it. No series can be created with it or moved to it (`422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`). The live ones were given the type they numbered most. - STANDARD: Standard invoice - SIMPLIFIED: Simplified invoice - CORRECTIVE: Corrects or cancels a previous standard invoice (R1–R4) - SIMPLIFIED_CORRECTIVE: Corrects a simplified invoice (R5). Spanish law requires R5 to be numbered apart from R1–R4, so they have a series type of their own - PROFORMA: Proforma (commercial document, non-fiscal numbering)
  - `name` string: Descriptive name of the series. Cannot be cleared.
  - `code` string: Alphanumeric series code (used in {CODIGO} variable). Allows uppercase letters, numbers, hyphens and underscores.
  - `description` string, nullable: Series description. Send `null` to clear it.
  - `format` string: Format template with available variables (UPPERCASE ONLY): - {CODIGO}: Series code (e.g., "FAC") - {YYYY}: Year with 4 digits (e.g., "2025") - {YY}: Year with 2 digits (e.g., "25") - {MM}: Month with 2 digits (e.g., "01") - {NUM}: Sequential number without padding (e.g., "1") - {NUM:X}: Sequential number with padding (e.g., {NUM:4} → "0001") **REQUIRED**: Must contain at least {NUM} or {NUM:X} **IMPORTANT**: Only uppercase (rejects {yy}, {mm}, {codigo}, etc.) Valid examples: - "{CODIGO}-{YYYY}-{NUM:4}" → "FAC-2025-0001" - "{CODIGO}/{NUM:6}" → "FAC/000001" - "{YYYY}{MM}-{NUM:3}" → "202501-001" The generated number is the invoice number sent to the AEAT, which accepts at most 60 printable ASCII characters and none of `"`, `'`, `<`, `>`, `=`. A format whose longest possible number breaks that rule is rejected with `422 SERIES_FORMAT_NUMBER_TOO_LONG` or `SERIES_FORMAT_INVALID_CHARACTERS`. The counter counts as at least 9 digits, with or without padding: `{NUM:X}` is a minimum width, not a maximum.
  - `counter_reset` 'NEVER' | 'ANNUAL' | 'MONTHLY': Counter reset policy: - NEVER: Counter never resets (continuous numbering) - ANNUAL: Counter resets yearly - MONTHLY: Counter resets monthly
  - `initial_number` integer: Initial number for this series counter. Only while the series has no issued invoices. It applies only to the first period in which the series issues an invoice: with `counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With `NEVER` there is a single period, so numbering simply continues from it.
  - `active` boolean: Whether the series is active. **Restriction:** A default series cannot be deactivated (another must be set as default first).
  - `default_series` boolean: Whether this is the default series. **Restriction:** An inactive series cannot be marked as default.

## Response `200`

Series updated successfully

- object: 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` boolean, required
  - `data` object, 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.
    - `id` string, uuid, required: Unique series ID
    - `document_type` 'UNASSIGNED' | 'STANDARD' | 'SIMPLIFIED' | 'CORRECTIVE' | 'SIMPLIFIED_CORRECTIVE' | 'PROFORMA', required: Document type associated with a series. Values mirror `InvoiceType`, so the series a document needs is named exactly like the document: - UNASSIGNED: Legacy value of series created before types existed. A series numbers only documents of its own type, so an `UNASSIGNED` series numbers none (`422 SERIES_INCOMPATIBLE_DOC_TYPE`); give it a type to keep using it. No series can be created with it or moved to it (`422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`). The live ones were given the type they numbered most. - STANDARD: Standard invoice - SIMPLIFIED: Simplified invoice - CORRECTIVE: Corrects or cancels a previous standard invoice (R1–R4) - SIMPLIFIED_CORRECTIVE: Corrects a simplified invoice (R5). Spanish law requires R5 to be numbered apart from R1–R4, so they have a series type of their own - PROFORMA: Proforma (commercial document, non-fiscal numbering)
    - `name` string, required: Descriptive name of the series
    - `code` string, required: Alphanumeric series code (used in {CODIGO} variable). Allows uppercase letters, numbers, hyphens and underscores.
    - `description` string: Optional series description
    - `format` string, required: Format template with available variables (UPPERCASE ONLY): - {CODIGO}: Series code (e.g., "FAC") - {YYYY}: Year with 4 digits (e.g., "2025") - {YY}: Year with 2 digits (e.g., "25") - {MM}: Month with 2 digits (e.g., "01") - {NUM}: Sequential number without padding (e.g., "1") - {NUM:X}: Sequential number with padding (e.g., {NUM:4} → "0001") **REQUIRED**: Must contain at least {NUM} or {NUM:X} **IMPORTANT**: Only uppercase (rejects {yy}, {mm}, {codigo}, etc.) Valid examples: - "{CODIGO}-{YYYY}-{NUM:4}" → "FAC-2025-0001" - "{CODIGO}/{NUM:6}" → "FAC/000001" - "{YYYY}{MM}-{NUM:3}" → "202501-001" The generated number is the invoice number sent to the AEAT, which accepts at most 60 printable ASCII characters and none of `"`, `'`, `<`, `>`, `=`. A format whose longest possible number breaks that rule is rejected with `422 SERIES_FORMAT_NUMBER_TOO_LONG` or `SERIES_FORMAT_INVALID_CHARACTERS`. The counter counts as at least 9 digits, with or without padding: `{NUM:X}` is a minimum width, not a maximum.
    - `counter_reset` 'NEVER' | 'ANNUAL' | 'MONTHLY', required: Counter reset policy: - NEVER: Counter never resets (continuous numbering) - ANNUAL: Counter resets yearly - MONTHLY: Counter resets monthly
    - `initial_number` integer: Initial number configured for this series counter: the number the series starts from. It applies only to the first period in which the series issues an invoice: with `counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With `NEVER` there is a single period, so numbering simply continues from it.
    - `active` boolean, required: Indicates whether the series is active
    - `default_series` boolean, required: Indicates whether this is the user's default series (only one can be)
    - `numbering_locked` boolean: `true` once this series has issued at least one invoice. From that point the fields that drive numbering (`code`, `format`, `counter_reset`, `initial_number`) are permanently locked and any attempt to change them is rejected — numbering can only be adjusted **before** the first invoice of a series. Cosmetic fields (`name`, `description`) remain editable. To use a different numbering, create a new series.
    - `created_at` string, date-time: Creation date
    - `next_number` integer: Next invoice number that will be assigned for this series
    - `updated_at` string, date-time: Last update date
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: The update is rejected. Error codes: `SERIES_CODE_LOCKED_HAS_INVOICES`, `SERIES_FORMAT_LOCKED_HAS_INVOICES`, `SERIES_RESET_LOCKED_HAS_INVOICES`, `SERIES_INITIAL_NUMBER_LOCKED_HAS_INVOICES`, `SERIES_DOCUMENT_TYPE_LOCKED_HAS_INVOICES` (the series already consumed a number), `DEFAULT_CANNOT_BE_UNMARKED`, `DEFAULT_CANNOT_DEACTIVATE`, `INACTIVE_CANNOT_BE_DEFAULT`, `REFERENCED_BY_PAYMENT_CONNECTION`, `REFERENCED_BY_RECURRING_INVOICE` (deactivating a series with a live —ACTIVE or PAUSED— recurring invoice template using it; finish it or switch it to another series first).
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: 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.
- `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`.
- `422`: The series is rejected. `error.code` is `VALIDATION_ERROR` when the body parses but a value is not acceptable: `details` is a flat map from the offending property's name (in the contract's `snake_case`) to a message describing what is wrong with it, or follows `FieldDeserializationError` when the value is outside an enum's vocabulary. Otherwise it is one of the series rules, among them: - `SERIES_MONTHLY_REQUIRES_MONTH_AND_YEAR` / `SERIES_ANNUAL_REQUIRES_YEAR` — the format cannot tell the counter reset periods apart. - `SERIES_INITIAL_NUMBER_OUT_OF_RANGE` — the initial number is out of range. - `SERIES_FORMAT_NUMBER_TOO_LONG` — the longest number the series can generate (the format with the code, the year, the month and the counter substituted) exceeds the 60 characters the AEAT accepts for the invoice number. The counter counts as at least 9 digits, with or without padding: `{NUM:X}` is a minimum width and the number keeps growing past it. - `SERIES_FORMAT_INVALID_CHARACTERS` — that number would contain a character the AEAT does not accept: only printable ASCII (32-126) is allowed, except `"`, `'`, `<`, `>` and `=`. - `SERIES_TYPE_CHANGE_LEAVES_TYPE_UNCOVERED` — changing `document_type` of the default series would leave its current document type, which still has other active series, without a default. Make one of those the default first. - `SERIES_COMPANY_DELETED` — the company was deleted while the request was running: its series keep their numbering but can no longer be activated or made the default.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

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