---
title: "Create an invoice series for a company"
method: POST
path: "/v1/companies/{company_id}/series"
tags: ["CompanySeries"]
---

# Create an invoice series for a company

`POST /v1/companies/{company_id}/series`

## Request

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

Creates an invoice series under a company.

- **Document type:** `document_type` is required. `UNASSIGNED` is rejected with
  `422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`: corrective invoices need a series of their own.
- **Code:** must be unique within the company; a code already taken answers `409`.
- **Unique numbers per issuer:** a series whose `code` and `format` could print a number
  that another series of the company (in the same environment, active or not) can also
  print is rejected with `409 SERIES_FORMAT_OVERLAPS`, naming that series. Proforma series
  are not compared.
- **Numbering:** `format` must contain `{NUM}` or `{NUM:X}` and only accepts uppercase
  tokens. `counter_reset` defaults to `ANNUAL`, so a format with no year token has to be
  sent with `counter_reset: NEVER`.
- **Default series:** the first series created for a document type is marked as default
  even if you send `default_series: false`.

## Headers

- `Idempotency-Key` string

## Request body

- CreateSeriesRequest: Request to create an invoice series. `document_type` is required: a new series always has a type, and `UNASSIGNED` is rejected with `422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`. Corrective invoices need a series of their own (`CORRECTIVE`).
  - `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, nullable: 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': 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. Useful when migrating from another system and wanting to continue existing numbering. For example, if the last invoices were 2024-0150, you can set initial_number=151. Default value is 1. 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
  - `default_series` boolean: Whether this is the default series for its document_type. **Auto-promotion:** the domain guarantees that, while at least one series exists for a given `(document_type, environment)`, exactly one of them is the default. So if you create the **first** series of a `document_type` (no default exists yet for that type and environment), it is marked as default **even if you send `false`** — the response will then return `default_series: true`. Send `true` to also unmark the current default of that type.

## Response `201`

Series created 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`: Bad request. From a dashboard session, a `BeeL-Environment` header whose value is not a known environment answers `ENVIRONMENT_HEADER_INVALID`, with the header named in `error.details` (`{"BeeL-Environment": "invalid"}`); it is never read as production. `BUSINESS_RULE_VIOLATION` is the generic answer to an operation the resource does not admit in its current state when that state has no code of its own. It carries no `details`; read the resource again before retrying.
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: Your account does not own or manage this company, or does not hold the required access over it. A NIF that does not exist answers the same way. That case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.
- `409`: The series clashes with another series of the company: `SERIES_CODE_DUPLICATED` (the code is taken) or `SERIES_FORMAT_OVERLAPS` (it could print numbers the other series prints).
- `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. Other `error.code` values with this status: - `SERIES_FORMAT_INVALID_PADDING` — the padding in `{NUM:X}` must be a whole number from 1 to 20 (e.g. `{NUM:4}` → `0001`). - `SERIES_FORMAT_NUM_REQUIRED` — the series `format` has no `{NUM}` or `{NUM:X}`, so it cannot number invoices. Add the counter. - `SERIES_FORMAT_UNRECOGNIZED_VARS` — the `format` uses a variable BeeL. does not know. Allowed: `{CODIGO}`, `{YYYY}`, `{YY}`, `{MM}`, `{NUM}`, `{NUM:X}`.
- `429`: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because too many went out to that recipient in a short time. Either way, retry after the `Retry-After` seconds. `QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan for the current period (issued invoices, for instance). Retrying does not help until the quota resets or the plan changes. `error.details` carries `feature_code`, `current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until the reset when it is known.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

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