{"openapi":"3.0.3","info":{"title":"BeeL. Public API","description":"Public invoicing API for self-employed professionals in Spain with VeriFactu regulatory compliance.\n\nAuthenticate using API Keys (`Authorization: Bearer <key>`). Keys are scoped and environment-aware:\n`beel_sk_live_*` targets production, `beel_sk_test_*` targets sandbox (no quota consumed, VeriFactu test mode).\n\nEvery operation declares the scope it requires in its `x-required-scopes` extension, and the\nreference at docs.beel.es shows it next to the operation's title. Create the key with (at least)\nthose scopes; a key that is short of one is answered `403`. The full catalogue is in the\n`ApiKeyAuth` security scheme below.\n\nOAuth2 is reserved for BeeL's own official clients (the MCP bridge and the CLI) and is not part of\nthe public contract at present. There is no dynamic client registration and no way for a third\nparty to obtain a `client_id`, so the API key is the credential to integrate with.\n\nPOST endpoints, and the PUT/PATCH/DELETE operations that list it, accept an `Idempotency-Key` header (any unique client-generated string) to retry a request safely without repeating it. A retry with the same key within 24 hours replays the first response when it was a success (2xx) or a server error (5xx), with the header `Idempotency-Replay: true`; a 4xx is not stored, so the corrected request can reuse the key. See the header parameter for the details.\n\n**This specification is served live by the API itself**, at\n`https://app.beel.es/api/v1/openapi.yaml` (and `.json`) — the same document this reference is\nbuilt from, so you can point a generator or a viewer straight at it.\n\n**Response envelope.** Every JSON response is wrapped in `{success, data, meta}`. Bare\nresources are not part of the contract. Read the payload through `data` and you never have to\nbranch on the shape of the body.\n\n- A **single resource** travels as an object in `data`: `data: {id, ...}`.\n- A **collection** travels under a named key inside `data`, alongside its `pagination`, also\n  inside `data`: `data: {invoices: [...], pagination: {...}}`. The key is named after the\n  resource, so a response never hides which collection it carries.\n- A collection is paginated unless the operation declares itself a **closed catalogue** — a\n  fixed, bounded list (tax types, customisation options) that has nothing to page through. Only\n  an explicit declaration in the operation makes a collection unpaginated; the absence of\n  `pagination` is never something to infer.\n- `GET /v1/accounts` paginates by **cursor** (`data: {accounts: [...], next_cursor}`) instead of\n  by page. That is a documented variant of pagination, not a different envelope: it is still a\n  named collection inside `data`.\n\nWhich of the three a collection is, is declared and machine-readable, so no consumer has to\nguess: the operation either carries `pagination` in its response schema, or the extension\n`x-closed-catalogue: true`, or `x-pagination-style: cursor`. Those three cases are the whole\nlist, and a collection that declares none of them is a defect in this contract, not a fourth\nvariant to code around. A contract ratchet in CI enumerates exactly that.\n\nA `data` holding the array directly, with `pagination` as its sibling, is arguably the nicer\nshape and is what a v2 of this contract would adopt. It is **not** what v1 does, and v1 will not\nbe flipped to it: live integrations read `data.<key>` today, and no ergonomic gain justifies\nbreaking them without a version to move to.\n\nOperations on their way out are the one exception, and they are frozen rather than fixed. An\noperation that is `deprecated` and announces a `Sunset` date keeps whatever shape it shipped\nwith — `GET /v1/products/search` is the last one whose `data` is a bare array — because\nreshaping a route that is about to be removed only breaks the integrations still on it. Its\ndescription says what the successor answers instead. Every operation without a retirement date\nobeys the rule above.\n\nQuery parameters are strict, across every endpoint. What is not universal yet is the grace window before\na name an operation does not declare starts being rejected, and the two families of routes sit on opposite\nsides of it.\n\nOn the **canonical routes** — `/v1/companies/{company_id}/…` and `/v1/accounts/{account_id}/…` — an\nunrecognised query parameter has always been rejected with `400` naming it. They are **excluded from the\ngrace window**: nothing changes for them on the date below, and no `BeeL-Warning` is ever emitted there.\nAn operation carrying a `Sunset` header is never canonical, whatever its shape — it is on its way out and\nkeeps the lax behaviour until it is removed.\n\nOn the **flat aliases** — `/v1/invoices`, `/v1/customers`, `/v1/products`, … — that same name is still\n**ignored**: the request returns `200`, but the filter, ordering or flag you sent was never applied — the\nmost common case being `per_page`, which is spelled `limit`. From **2026-09-03** the aliases answer like the\ncanonical routes: `400` naming the parameter, instead of ignoring it. Until then, any alias response that\ndropped a parameter carries a `BeeL-Warning` header naming it, so you can find the affected requests before\nthe change.\n\nA value outside a parameter's vocabulary — an unknown enum value, a malformed UUID — is likewise rejected\nwith `400` (`VALIDATION_ERROR`, with `error.details` following `FieldDeserializationError`: `field`,\n`invalid_value` and, for an enum, `allowed_values`). The fault is in the request target, not in the request\ncontent, which is what `422` is defined over; `422` is reserved for a request body that is syntactically\nvalid but semantically wrong. A parameter that does parse but whose value the operation will not take — a\n`limit` under its minimum, a `cursor` that is not one of ours — is the one crossover: it is answered `422`,\nand the operations where that happens declare it.\nA parameter whose name or value carries a null character (U+0000, sent as `%00`) is rejected the same way,\non every operation: `400` (`VALIDATION_ERROR`), with `error.details` naming the parameter.\n\n**Request bodies** are read leniently where query parameters are read strictly: a JSON property the\noperation does not declare is **ignored**, on every route, and the request proceeds as if it had not been\nsent. No `400` and no warning header names it, so a misspelled property is silent — check the resource the\noperation returns rather than the status code to confirm a field was applied. What is declared is judged\nin two steps:\n\n- **Shape** — the body is not JSON, or a property has the wrong type or format (a string where a number\n  is expected, a date that does not parse, a malformed UUID): `400` (`INVALID_JSON_FORMAT`).\n  `error.details` follows `FieldDeserializationError` and names the `field`, the `invalid_value` and,\n  where there is one, the `expected_format`. When the value has the wrong JSON type, `invalid_value` is\n  the kind received (`string`, `number`, `boolean`, `array`, `object`, `null`) rather than the value\n  itself, and `expected_format` is the kind expected (`\"lines\": \"no\"` gives `string` and `array`).\n- **Content** — the body parses but a value is not acceptable: `422` (`VALIDATION_ERROR`). A value outside\n  an enum's vocabulary belongs here, not under `400`: the property is a well-formed string, it just names\n  nothing the operation knows, so `error.details` again follows `FieldDeserializationError`, with the\n  `allowed_values`. For every other constraint — a missing required property, a length or range that is\n  not respected, a rule between properties — `error.details` is a flat map from the property's name (in\n  the contract's `snake_case`, nested paths joined with a dot) to a message describing what is wrong with\n  it, one entry per offending property. Business rules that fail with a code of their own (`EMISSION_NOT_READY`,\n  `NIF_INVALID_CONTROL_DIGIT`, …) are also `422`, and the operation declares them.\n- **Null character** — no text in the body may carry U+0000 (`\"\\u0000\"` in JSON). It is well-formed JSON,\n  but no string BeeL stores can hold it, so it is judged as content: `422` (`VALIDATION_ERROR`), with\n  `error.details` keyed by the property's path as sent (`lines[0].description`, `metadata.order_ref`).\n  This check reads the whole body, so it applies to a property the operation does not declare too.\n\n**Transport-level errors.** Four answers come from the HTTP layer, before anything above is judged, and\nthey can reach any operation:\n\n- `400` (`BAD_REQUEST`) — the URL cannot be read: a character that must be percent-encoded arrives raw\n  (`|`, `\\`), or the request line is too long. Percent-encode the query values and shorten\n  the URL.\n- `405` (`METHOD_NOT_ALLOWED`) — the path exists, but not with that method. The `Allow` header of the\n  response lists the methods it does have.\n- `406` (`NOT_ACCEPTABLE`) — your `Accept` header asks for a media type the operation does not produce.\n  Send `application/json`, or send no `Accept` at all.\n- `415` (`UNSUPPORTED_MEDIA_TYPE`) — the body's `Content-Type` is not one the operation consumes. Sending\n  a body with no `Content-Type` lands here too.\n\nThese four, and `429` below, carry the same error envelope as every other error — `success`, `error.code`,\n`error.message`, plus the RFC 9457 fields — so a client that reads `error.code` handles them like the rest.\nRather than repeat them operation by operation, every operation declares a `default` response that covers\nthem and anything a future version starts returning: a generated client always has a branch to fall into.\n\n<!-- generated:rate-limits — cloudflare/bin/render-rate-limit-prose.js -->\n**Rate limiting.** Limits are enforced at the network edge, on a fixed 60-second window, and\nindependently at each edge location, so short bursts may briefly exceed the nominal figure before\nthrottling engages: treat each figure as the rate at which you may start receiving `429` responses,\nnot as an exact quota. What the window is counted against depends on the tier. The **Standard**\ntier counts per credential: with an **API key**, per key — the key is the bucket, and nobody else\nspends it; with a **dashboard session**, per client. If you integrate at any volume,\nauthenticate with an API key: it is the only credential with a Standard budget of its own. The\n**Strict** and **Global** tiers always count per originating IP address, whatever credential you\npresent, so several keys calling from the same host share those two budgets. Three tiers apply and\na single request may count against more than one:\n\n| Tier | Limit | Applies to |\n| --- | --- | --- |\n| Global | 1000 req / 60 s | Every request to the API |\n| Standard | 300 req / 60 s | `/v1/accounts/imports`, `/v1/companies/{company_id}/customers`, `/v1/companies/{company_id}/invoices`, `/v1/customers`, `/v1/invoices`, `/v1/me`, `POST /v1/nif/validate` — each including its sub-paths |\n| Strict | 5 req / 60 s | `/v1/companies/{company_id}/logo`, `POST /v1/accounts/{account_id}/companies/{company_id}/representation/generate`, `POST /v1/accounts/{account_id}/companies/{company_id}/representation/submit`, `POST /v1/accounts/{account_id}/invitations`, `POST /v1/companies/{company_id}/invoices/deliveries`, `POST /v1/companies/{company_id}/invoices/{invoice_id}/send`, `POST /v1/companies/{company_id}/representation`, `POST /v1/invoices/bulk/send` — each including its sub-paths |\n\nExceeding a limit returns `429` with the error code `RATE_LIMIT_EXCEEDED` and the `Retry-After`,\n`RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset` headers. Successful responses carry\nno `RateLimit-*` header, so a client cannot track its remaining budget ahead of time: retry on\n`429`, honouring `Retry-After`.\n<!-- /generated:rate-limits -->\n\n### Request body size\n\nA request body above **2 MB** is rejected with `413` and the error code\n`REQUEST_BODY_TOO_LARGE`; `error.details` carries the applicable maximum and the size the\nrequest declared, except when the HTTP connector cuts the request first, which carries no\n`details`. A body above **8 MB** is rejected at the network edge, before the request\nreaches the API, with `413` and the error code `PAYLOAD_TOO_LARGE`. File uploads are\nadditionally bound by their own, lower per-endpoint limit, documented on each operation and\nreported as `FILE_TOO_LARGE`.\n\nThe largest body the API expects is a bulk import at its declared `maxItems`, which stays\ncomfortably under the 2 MB figure; if you are approaching it, split the batch.\n","version":"1.9.0","contact":{"name":"BeeL. Technical Team","email":"it@beel.es","url":"https://beel.es"},"license":{"name":"Proprietary","url":"https://beel.es/license"},"x-agent-resources":{"openapiJson":"https://beel.es/openapi.json","openapiYaml":"https://beel.es/openapi.yaml","source":"https://docs.beel.es/api/openapi","authorizationServerMetadata":"https://app.beel.es/api/.well-known/oauth-authorization-server","llmsFull":"https://docs.beel.es/llms-full.txt","mcpServer":"https://mcp.beel.es/mcp"},"x-onboarding":{"signupUrl":"https://app.beel.es/signup","selfServeApiKeys":true,"freeTier":"sandbox ilimitado, sin tarjeta","sandbox":{"keyPrefix":"beel_sk_test_","sameBaseUrl":true,"aeatTestMode":true,"quotaConsumed":false},"liveKeyPrefix":"beel_sk_live_","salesContactRequired":false,"zeroAuthEndpoints":["https://beel.es/openapi.json","https://beel.es/api/health","https://beel.es/agents.md"]},"x-cli":{"package":"@beel_es/cli","npm":"https://www.npmjs.com/package/@beel_es/cli","quickstart":"npx @beel_es/cli --help"},"x-agent-instructions":"https://beel.es/agents.md"},"servers":[{"url":"https://app.beel.es/api","description":"Production server"}],"security":[{"ApiKeyAuth":[]},{"OAuth2":[]}],"tags":[{"name":"CompanyInvoices","description":"The invoices of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/invoices`. The NIF in the path is the only source of context: the account that owns it is derived from it. It is the canonical form of the operations that `Invoices` still exposes under the flat `/v1/invoices`."},{"name":"CompanyInvoiceLifecycle","description":"State transitions and scheduling of a NIF's invoices (issue, void, corrective, status, schedule), under `/v1/companies/{company_id}/invoices/{invoice_id}/...`. Canonical form of `InvoiceLifecycle`."},{"name":"CompanyInvoiceDelivery","description":"Delivery, bulk operations and exports of a NIF's invoices (send, deliveries, batches, exports, PDF archive), under `/v1/companies/{company_id}/invoices/...`. Canonical form of `InvoiceDelivery`."},{"name":"Invoices","description":"Invoice CRUD operations"},{"name":"InvoiceLifecycle","description":"Invoice state transitions and scheduling"},{"name":"InvoiceDelivery","description":"Invoice delivery, bulk operations and exports"},{"name":"CompanyProforma","description":"Converting a NIF's proforma into a fiscal invoice (`POST /v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice`). The rest of the proforma workflow happens through the regular `CompanyInvoices` operations with `type=PROFORMA` — see the `Proforma` tag for the full model."},{"name":"Proforma","description":"A proforma is a non-fiscal commercial document, a formal quote you send a customer\nbefore invoicing. It lives on the same `/invoices` resource as any other document, so\nyou work with it through the invoice endpoints using `type=PROFORMA`. There is no\nseparate proforma resource.\n\nA proforma never enters VeriFactu, so it carries no QR and is never submitted to the\nAEAT, and it consumes no invoice quota. It is numbered from a dedicated non-fiscal\nseries, so its number always looks like `PRO-2026-0001`.\n\nA proforma has a single working state, `ACTIVE`. It does not go through the fiscal\ndraft and issue cycle:\n\n* **Create** it with `type=PROFORMA` and it is born `ACTIVE`, already numbered. It is\n  editable and deletable, and there is no separate issue step (`issue_directly` is\n  ignored).\n* **Convert** it once the customer accepts. It becomes `CONVERTED` and a new `STANDARD`\n  invoice is created in `DRAFT`, linked back through `source_proforma_id`.\n* **Void** it if the offer is rejected or withdrawn (`POST /v1/invoices/{invoice_id}/void`).\n  It becomes `VOIDED` with no corrective invoice and nothing sent to the AEAT, and it\n  stays on record.\n* **`EXPIRED`** is a derived state for an `ACTIVE` proforma whose `valid_until` has\n  passed. It is never stored, so the proforma stays `ACTIVE` underneath and remains\n  convertible.\n\nProformas do not appear in the default invoice list, which returns fiscal documents only.\nUse `type=PROFORMA` to list them.\n"},{"name":"Customers","description":"Customer management"},{"name":"Customer Import","description":"Bulk creation, CSV import, and Holded import operations"},{"name":"CompanyCustomers","description":"The customers of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/customers`. The NIF in the path is the only source of context: the account that owns it is derived from it. It is the canonical form of the operations that `Customers` and `Customer Import` still expose under the flat `/v1/customers`."},{"name":"CompanyCustomerImports","description":"Importing customers into a company, addressed by `/v1/companies/{company_id}/customers/imports`. The origin of the file travels in the body as `source`, so adding an origin never adds a route, and previewing is a separate operation with no effects instead of a boolean flag."},{"name":"AccountImports","description":"Onboarding managed accounts in bulk from a file, addressed by `/v1/accounts/imports`. The file is declarative and the import is re-runnable: each pass applies only what is missing, and previewing is a separate operation with no effects instead of a boolean flag."},{"name":"Customer Templates","description":"Templates and sample files for customer imports"},{"name":"Templates","description":"Downloadable file templates. They are fixed files, the same for every credential, and belong to no account and no NIF."},{"name":"Products","description":"Product and service catalog management"},{"name":"CompanyProducts","description":"The product/service catalog of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/products`. The NIF in the path is the only source of context: the account that owns it is derived from it. It is the canonical form of the operations that `Products` still exposes under `/v1/products`, and searching is `?q=` on the collection instead of a path of its own."},{"name":"Members","description":"Account member management (multi-NIF). Members and their company grants are **shared between Test and Live**: a collaborator is the same person, with the same role and the same grants, in both environments — switching environment changes which data you see, not who may reach it. Reading them works with either API key; changing them requires a live API key or the dashboard (`403 LIVE_CREDENTIAL_REQUIRED` otherwise), because the change lands on the real account. **One exception**: handing the account over to another member (`PUT /v1/accounts/{account_id}/owner`) is the dashboard's alone — no API key can do it, the live one included (`403 OPERATION_REQUIRES_SESSION`), so that a leaked key cannot take the account. An integration that provisions accounts hands ownership over with a claim token instead (`POST /v1/accounts/{account_id}/claim-tokens`)."},{"name":"Invitations","description":"Member invitations — invite people to the account and revoke pending invites (multi-NIF). Like members, invitations are **shared between Test and Live**: accepting one grants real access, so creating and revoking them requires a live API key or the dashboard (`403 LIVE_CREDENTIAL_REQUIRED` otherwise). Listing and retrieving work with either key."},{"name":"Accounts","description":"Account provisioning for partners managing multiple accounts and NIFs. The whole onboarding flow — provisioning, claim tokens and usage — works with either API key: the account is born inert (no NIF switched on, no keys, no owner), which is exactly what you are meant to rehearse in Test. Changing a managed account's access level and ending its management require a live API key or the dashboard (`403 LIVE_CREDENTIAL_REQUIRED` otherwise): both act on an account that may already be invoicing, and ending management cannot be undone."},{"name":"Provisioning","description":"Lifecycle view for accounts you provisioned: track their activation state and manage the access level you hold over them."},{"name":"ConfigurationTax","description":"Tax configuration and tax type catalogs"},{"name":"TaxTypes","description":"The tax regimes, percentages, withholdings and exemption reasons BeeL accepts. The same for every credential, and readable before the first NIF exists."},{"name":"InvoiceSeries","description":"Invoice series management"},{"name":"ConfigurationVeriFactu","description":"VeriFactu compliance configuration"},{"name":"ConfigurationPreferences","description":"User language preferences"},{"name":"InvoiceCustomization","description":"The PDF templates a NIF can be rendered with, each one with a readable, translated name. The same for every account."},{"name":"NIF","description":"Tax ID validation against AEAT tax registry"},{"name":"Company","description":"The company as a resource of its own, addressed by `/v1/companies/{company_id}`. The NIF in the path is the only source of context: the account that owns it is derived from it."},{"name":"CompanyBranding","description":"The invoice branding of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/logo` and `/v1/companies/{company_id}/invoice-customization`. The NIF in the path is the only source of context: the account that owns it is derived from it."},{"name":"CompanyVeriFactuConfiguration","description":"The VeriFactu configuration of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/verifactu-configuration`. The NIF in the path is the only source of context. It is the canonical form of what `ConfigurationVeriFactu` still exposes under `/v1/configuration/verifactu`."},{"name":"CompanyTaxConfiguration","description":"The tax configuration of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/tax-configuration`. The NIF in the path is the only source of context. It is the canonical form of what `ConfigurationTax` still exposes under `/v1/configuration/taxes`."},{"name":"CompanySeries","description":"The invoice series of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/series`. The NIF in the path is the only source of context. It is the canonical form of what `InvoiceSeries` still exposes under `/v1/configuration/series`."},{"name":"CompanyRepresentation","description":"The AEAT fiscal representation of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/representation`. The NIF in the path is the only source of context: the account that owns it is derived from it. It is the canonical form of the operations that `PublicCompanyRepresentations` still exposes under `/v1/accounts/{account_id}/companies/{company_id}/representation/…`, reshaped as one REST resource — `GET` reads the state, `POST` creates the document, `DELETE` cancels it — instead of five verbs in the URL."},{"name":"CompanyActivations","description":"Switching a company on or off in Test or Live, as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/activations`. The NIF in the path is the only source of context: the account that owns it is derived from it."},{"name":"CompanyPaymentConnections","description":"The payment provider connections of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/payment-connections`. The NIF in the path is the only source of context: the account that owns it is derived from it. Opening a connection is the sibling sub-resource `…/payment-connections/authorizations`, because it returns an authorization URL and not the connection the collection lists."},{"name":"CompanyPaymentEvents","description":"The payment events of a company connection, addressed by `/v1/companies/{company_id}/payment-connections/{connection_id}/events`. The NIF in the path is the only source of context: the account that owns it is derived from it. Events hang from the connection the `{connection_id}` segment picks, so only the events of that NIF's connection are reachable."},{"name":"AccountCompanies","description":"Listing and creating the NIFs of an account, under `/v1/accounts/{account_id}/companies`. Operating on one NIF is the `Company` resource."},{"name":"PublicCompanyRepresentations","description":"**Deprecated.** VeriFactu representation flow for digital signature and AEAT registration,\nin its account-nested form. Use `CompanyRepresentation`\n(`/v1/companies/{company_id}/representation`), which behaves identically; these routes keep\nworking until the date announced in their `Sunset` response header.\n\n**Complete SaaS integration flow:**\n1. `POST /v1/accounts/{account_id}/companies` — Create company/NIF\n2. `POST /v1/companies/{company_id}/representation` — Generate unsigned PDF\n3. `GET /v1/companies/{company_id}/representation/document` — Download PDF for signing\n4. `POST /v1/companies/{company_id}/representation/submit` — Upload signed PDF\n5. `GET /v1/companies/{company_id}/representation` — Check VeriFactu status\n"},{"name":"RecurringInvoices","description":"Recurring invoice template management and automated invoice generation.\n\nCreate templates that automatically generate invoices on a schedule.\nSupports monthly, quarterly and yearly recurrence, pause/resume, skip,\nmanual generation, and invoice preview.\n"},{"name":"CompanyRecurringInvoices","description":"The recurring invoice templates of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/recurring-invoices`. The NIF in the path is the only source of context: the account that owns it is derived from it. It is the canonical form of the operations that `RecurringInvoices` still exposes under `/v1/recurring-invoices`."},{"name":"AccountWebhooks","description":"Outbound webhook subscriptions as a sub-resource of the account itself, addressed by `/v1/accounts/{account_id}/webhooks`. The account in the path is the only source of context — the credential does not decide it, and the `BeeL-Active-Company` header plays no part. It is the canonical form of the operations that `Webhooks` still exposes under `/v1/webhooks`."},{"name":"Webhooks","description":"Outbound webhook subscriptions for real-time event notifications.\n\nRegister HTTPS endpoints to receive event payloads when key actions occur\n(e.g. VeriFactu submission accepted/rejected by AEAT).\n\nAll deliveries are signed with HMAC-SHA256 (`BeeL-Signature` header).\nFailed deliveries are retried on a fixed schedule: 7 attempts over about 3 days\n(3 attempts over about 11 minutes in sandbox); a `4xx` other than `408` and `429` is not\nretried.\n"},{"name":"AccountRequestLogs","description":"Canonical form of the request logs, addressed by `/v1/accounts/{account_id}/request-logs`. The account in the path authorizes the call; the logs returned are always the requests made by you, with any of your API keys in the environment you authenticate from — never the whole account's traffic, and never another user's. It replaces the operations that `RequestLogs` still exposes under `/v1/developers/request-logs`."},{"name":"RequestLogs","description":"Read-only access to the API request logs generated by your own API keys\n(live + test). Inspect method, path, status, timing, and full request/response\ndetail of past calls. Cursor-paginated; sourced from observability storage.\n"},{"name":"Emails","description":"History of emails the system has sent on your behalf (invoices,\nverification, onboarding, etc.). Read-only.\n"},{"name":"AccountEmails","description":"The sent-email history of an account, addressed by `/v1/accounts/{account_id}/emails` and `/v1/accounts/{account_id}/email-indicators`. The account in the path is the only source of context, instead of being derived from the credential. It is the canonical form of the operations that `Emails` still exposes under `/v1/emails`."},{"name":"Identity","description":"Who am I (`GET /v1/me`): the authenticated principal — credential type, environment it operates on and the permissions it holds — so a client can adapt what it offers instead of discovering the limits through a `403`."},{"name":"Me","description":"Preferences of the authenticated principal (`PATCH /v1/me`), such as the language BeeL uses to talk to the account."},{"name":"CompanyFiscalSummary","description":"The fiscal summary of a company as a sub-resource of the company itself, addressed by `/v1/companies/{company_id}/fiscal-summary`. The NIF in the path is the only source of context: the account that owns it is derived from it."}],"paths":{"/v1/me/identity":{"get":{"tags":["Identity"],"summary":"Get my identity","description":"Returns the identity of the authenticated principal: the account the credential belongs to,\nthe person's email, name, logo and interface language, and a description of the credential\nitself. Unlike every other operation, it requires no scope — any valid credential\nresolves, so a `200` confirms the credential works and tells you which account it belongs\nto, and a `401` that it does not.\n\n- **`account_id`:** identifies who the credential belongs to, not what it is currently\n  pointed at; selecting a different company with `BeeL-Active-Company` does not change it.\n- **`name`:** resolves as `trade_name ?? legal_name` of the active fiscal profile, and is\n  `null` until onboarding creates one.\n- **`credential`:** describes the credential the call was authenticated with — its type,\n  the environment it operates on and the permissions it holds — so a client can adapt what\n  it offers instead of discovering the limits through a `403`.\n- **Caching:** responses are never cached (`Cache-Control: no-store`).\n","operationId":"getMyIdentity","x-required-scopes":[],"security":[{"ApiKeyAuth":[]},{"SessionCookie":[]},{"OAuth2":[]}],"responses":{"200":{"description":"Identity of the authenticated principal","headers":{"Cache-Control":{"description":"Always `no-store` (personal data, cross-origin consumers)","schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MyIdentity"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential is genuine, but the account's plan does not include API access (`error.code` = `FEATURE_NOT_AVAILABLE`, `error.details.feature_code` = `api_access`). Whoami requires no scope, so for an API key this is the only refusal besides `401`: without `api_access` the key reaches no public endpoint at all, and saying so here — rather than a `401` that reads as \"wrong key\" — tells a health-check the credential is fine and the plan is not. Browser sessions never get this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"FEATURE_NOT_AVAILABLE","message":"Your plan does not include api_access","details":{"feature_code":"api_access","current_plan":"FREE"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/me":{"patch":{"tags":["Me"],"summary":"Update my preferences","description":"Updates the preferences of the authenticated person. Today the only mutable\npreference is `language`.\n\nIt applies to the interface, to template names and colours in invoice\ncustomisation, and to the emails the person receives. It belongs to the person,\nnot to a fiscal profile: the languages of invoices and of emails are separate\nsettings of each company.\n","operationId":"updateMe","x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMeRequest"},"example":{"language":"ca"}}}},"responses":{"200":{"description":"Preferences updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/MyPreferences"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices":{"get":{"tags":["Invoices"],"summary":"List invoices","description":"Returns a paginated list of invoices, with filters, sorting and pagination.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/invoices`, which returns the same\n  list with the same filters.\n- **Difference:** the legacy alias `external_reference` is not carried over there. Use\n  `external_ref`, which this route also accepts.\n\n**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.\n","operationId":"listInvoices","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/invoices"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"$ref":"#/components/parameters/ActiveCompanyHeader"},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"search","in":"query","description":"Global search across invoice number, recipient name, recipient NIF, and series code (partial, case-insensitive)","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by invoice status. Accepts a comma-separated list to match any of several\nstatuses, for example `status=DRAFT,ISSUED`. A single value is also valid. An empty\nvalue (`status=`) is the same as omitting the parameter.\n","schema":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceStatus"}},"style":"form","explode":false},{"name":"payment_method","in":"query","description":"Filter by payment method. Accepts a comma-separated list to match any of several\nmethods, for example `payment_method=DIRECT_DEBIT,CARD`. A single value is also valid.\n`NONE` also matches invoices that have no payment method stored. Combine it with\n`date_from`/`date_to` to list, for example, the direct debits of a month. An empty\nvalue (`payment_method=`) is the same as omitting the parameter.\n","schema":{"type":"array","items":{"$ref":"#/components/schemas/PaymentMethod"}},"style":"form","explode":false},{"name":"type","in":"query","description":"Filter by invoice type","schema":{"$ref":"#/components/schemas/InvoiceType"}},{"name":"fiscal_only","in":"query","description":"When `true` (the default), returns only fiscal documents (STANDARD, CORRECTIVE,\nSIMPLIFIED), excluding proformas and any other non-fiscal document. Set it to `false`\nto list every document type. Ignored when an explicit `type` is given: use\n`type=PROFORMA` to list proformas.\n","schema":{"type":"boolean","default":true}},{"name":"customer_id","in":"query","description":"Filter by customer UUID","schema":{"$ref":"#/components/schemas/UUID"}},{"name":"date_from","in":"query","description":"Date from, inclusive (YYYY-MM-DD). It applies to the operation date when set, and to the\nissue date otherwise.\n","schema":{"type":"string","format":"date"}},{"name":"date_to","in":"query","description":"Date to, inclusive (YYYY-MM-DD). Applies to the same date as `date_from`.","schema":{"type":"string","format":"date"}},{"name":"invoice_number","in":"query","description":"Search by invoice number (e.g., 2025/0001)","schema":{"type":"string"}},{"name":"recipient_name","in":"query","description":"Filter by recipient's fiscal name (partial, case-insensitive search)","schema":{"type":"string"}},{"name":"recipient_nif","in":"query","description":"Filter by recipient's NIF (partial search)","schema":{"type":"string"}},{"name":"series_code","in":"query","description":"Filter by series code (exact match, case-insensitive). Use `search` for partial matching across the invoice number, recipient and series code.","schema":{"type":"string"}},{"name":"external_ref","in":"query","description":"Filter by exact external reference (client-supplied order/cart/contract id).\n\nThis parameter was previously named `external_reference`. The old name is still accepted\nfor backwards compatibility (see `external_reference` below) and will be withdrawn in a\nfuture major version — send `external_ref`.\n","schema":{"type":"string"}},{"name":"external_reference","in":"query","deprecated":true,"description":"**Deprecated** — former name of `external_ref`, still honoured so existing integrations\nkeep working. Ignored when `external_ref` is also present. Use `external_ref`.\n","schema":{"type":"string"}},{"name":"taxable_base_min","in":"query","description":"Minimum taxable base","schema":{"type":"number","format":"double"}},{"name":"taxable_base_max","in":"query","description":"Maximum taxable base","schema":{"type":"number","format":"double"}},{"name":"total_min","in":"query","description":"Minimum invoice total","schema":{"type":"number","format":"double"}},{"name":"total_max","in":"query","description":"Maximum invoice total","schema":{"type":"number","format":"double"}},{"name":"verifactu_status","in":"query","description":"Filter by the VeriFactu submission status of the invoice, using the very same\nvocabulary that `verifactu.submission_status` publishes on each invoice.\n`NOT_SUBMITTED` selects issued invoices with VeriFactu enabled whose\nregistration never happened (no live record).\n\nOnly invoices with VeriFactu enabled can match. To select the ones outside the\naxis (VeriFactu disabled), use `verifactu_enabled=false` instead.\n","schema":{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"}},{"name":"verifactu_enabled","in":"query","description":"Filter by whether VeriFactu is enabled for the invoice — the same flag published as\n`verifactu.enabled`. `false` returns the invoices that never reach AEAT.\n","schema":{"type":"boolean"}},{"name":"metadata","in":"query","description":"Filter by metadata key/value pairs (exact match, AND between keys).\nRepeat the bracket-style param to filter on multiple keys.\nMax 50 pairs per request. Keys must match `^[A-Za-z0-9_\\-.]{1,64}$`.\nExample: `?metadata[external_order_id]=ORD-42&metadata[tenant]=acme`\n","required":false,"style":"deepObject","explode":true,"schema":{"type":"object","additionalProperties":{"type":"string"},"maxProperties":50}},{"name":"sort_by","in":"query","description":"Field to sort by: `issue_date` (default), `operation_date`, `due_date`,\n`invoice_number`, `series_code`, `status`, `invoice_total`, `taxable_base`,\n`total_vat`, `total_equivalence_surcharge`, `total_discounts`, `recipient_name`,\n`recipient_nif`, `created_at` or `updated_at`. Any other value is rejected with `400`\n`VALIDATION_ERROR`, whose `details` name `sort_by` and the accepted values.\n`invoice_number` sorts by series code and then by the number within the series,\ncompared numerically (`F-9` before `F-10`). Ties on any field are broken by series\ncode, then by the number within the series and then by id, in the requested direction.\n","schema":{"type":"string"}},{"name":"sort_order","in":"query","description":"Sort direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"desc"}}],"responses":{"200":{"description":"Invoice list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["invoices","pagination"],"properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"examples":{"list_success":{"$ref":"#/components/examples/list-invoices-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"A parameter parsed as its declared type but its value is not one the operation admits:\na `limit` outside its range (`limit=0`, `limit=101`), a `page` under 1, a `cursor` that is\nnot one this API issued, more keys than the `metadata` filter accepts, a value outside the\nsupported set. The query was rejected whole and nothing was read.\n\nWhere a `400` says the target could not be understood, this says it was understood and\nrefused. Fix the value — retrying the same query answers the same way.\n\nOther `error.code` values with this status:\n\n- `METADATA_FILTER_TOO_MANY_KEYS` — the query sends more than 50 distinct `metadata[key]` filters; narrow the filter to 50 keys or fewer.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Validation constraint violation.","details":{"limit":"must be greater than or equal to 1"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["Invoices"],"summary":"Create an invoice","description":"Creates an invoice, optionally numbered and issued in the same call with\n`options.issue_directly: true`.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices`, which behaves\n  identically.\n\n**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.\n","operationId":"createInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/ActiveCompanyHeader"},{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"wait_for_pdf","in":"query","required":false,"description":"Same flag as `options.wait_for_pdf`, accepted here too so both routes to a PDF take it\nin the same place — `POST /v1/invoices/{invoice_id}/issue` declares it as a query param\nand this one used to accept it only inside the body.\n\nOnly applies when the invoice is issued in this call (`options.issue_directly: true`).\nIf `true`, waits for PDF generation and returns the URL in the response (~1-3s). It is\nenough to ask for it in one of the two places.\n","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceRequest"},"examples":{"basic_invoice":{"$ref":"#/components/examples/create-basic"},"adhoc_invoice":{"$ref":"#/components/examples/create-with-adhoc-customer"},"issue_directly":{"$ref":"#/components/examples/create-issue-directly"},"custom_email":{"$ref":"#/components/examples/create-with-custom-email"},"simplified_under_3000":{"$ref":"#/components/examples/under-3000"},"standard_spanish_b2b":{"$ref":"#/components/examples/spanish-b2b"},"standard_multiple_vat":{"$ref":"#/components/examples/multiple-vat-rates"},"line_by_declared_total_out_of_range":{"$ref":"#/components/examples/derived_unit_price_out_of_range_request"},"standard_irpf":{"$ref":"#/components/examples/irpf-withholding"},"standard_recargo":{"$ref":"#/components/examples/recargo-equivalencia"},"standard_eu_goods_e5":{"$ref":"#/components/examples/b2b-intra-eu-goods-e5"},"standard_eu_services_n2":{"$ref":"#/components/examples/b2b-intra-eu-services-n2"},"standard_b2c_oss_over":{"$ref":"#/components/examples/b2c-oss-over-threshold"},"standard_b2c_oss_under":{"$ref":"#/components/examples/b2c-under-oss-threshold"},"standard_export_e2":{"$ref":"#/components/examples/export-non-eu-goods-e2"},"standard_services_noeu":{"$ref":"#/components/examples/services-non-eu-n2"},"standard_exempt_art20":{"$ref":"#/components/examples/exempt-art-20-medical"},"standard_igic":{"$ref":"#/components/examples/igic-canary-islands"},"standard_not_subject":{"$ref":"#/components/examples/not-subject-art-7-9"},"standard_isp_reverse":{"$ref":"#/components/examples/isp-reverse-charge-s2"},"standard_rebu":{"$ref":"#/components/examples/rebu-used-goods"}}}}},"responses":{"201":{"description":"Invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"created_success":{"$ref":"#/components/examples/created-success"}}}}},"400":{"description":"One of:\n- `INVALID_JSON_FORMAT`: the body is not valid JSON, or a property has the wrong type or\n  format. `details` follows `FieldDeserializationError`: `field`, `invalid_value` and, where\n  there is one, `expected_format`.\n- `SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`: a `SIMPLIFIED` invoice whose total, VAT\n  included, is above 3,000€. The same answer when the invoice is created and when an edit\n  takes it over the cap, whether the edit changes the lines or changes `type` to\n  `SIMPLIFIED`. Issue a `STANDARD` invoice with the customer identified instead.\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `INVOICE_NO_LINES` — the invoice was sent with an empty `lines` array; send at least one line.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_FOUND` — an id in `source_invoice_ids` does not exist, is in another environment, or is not visible to this account or its managed accounts.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_ISSUED` — a source invoice in `source_invoice_ids` is still a draft (or not in an issued state); issue it first.\n- `LINE_SUPLIDO_TOO_MANY_SOURCES` — more than 200 distinct `source_invoice_ids` in one request; split the suplido across invoices.\n- `LINE_SUPLIDO_TOO_MANY_SOURCE_ACCOUNTS` — the `source_invoice_ids` belong to more than 50 managed accounts; split the request.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECIPIENT_NIF_AND_ID_OTHER_EXCLUSIVE` — the inline recipient carries both `nif` and `alternative_id`; send only one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalid_json_format":{"summary":"A property with the wrong format","value":{"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"}}},"error_simplified_exceeds_limit":{"$ref":"#/components/examples/exceeds_3000"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Branch on `error.code`:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict. One of:\n- Idempotency key already processed, or duplicate invoice number.\n- `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`: a live invoice with the same\n  `external_ref` already exists. Fetch it via\n  `GET /v1/invoices?external_ref=...`. Deleting it lets you recreate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"conflict":{"$ref":"#/components/examples/conflict-409"}}}}},"422":{"description":"Validation error, or (when creating and issuing directly) the company/NIF is not ready\nto issue in this environment. In the latter case `error.code` is `EMISSION_NOT_READY`\nand `error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`, `NIF_NOT_REGISTERED`,\n`NIF_REPRESENTATION_REQUIRED`). When a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use. Sandbox applies the same gate as production, except\n`NIF_REPRESENTATION_REQUIRED`: the signed representation is only required when the\noperation reaches the real tax authority.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAt issue time the generated number must also fit the AEAT invoice number: more than 60\ncharacters answers `INVOICE_NUMBER_TOO_LONG`, a character outside printable ASCII or one\nof `\"`, `'`, `<`, `>`, `=` answers `INVOICE_NUMBER_INVALID_CHARACTERS`. In both cases the\ninvoice is not issued and the number is not used; issue it with another series, or fix the\nseries code or format while the series has no issued invoices, and retry.\nA SIMPLIFIED invoice whose recipient carries a NIF or an alternative identifier is\nrejected with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`, at any amount: issue a\nSTANDARD invoice instead.\nWhen the invoice is recorded with VeriFactu, its tax breakdown must also fit one AEAT\nrecord: lines are grouped by tax, regime key, operation type, exemption, tax rate and\nequivalence surcharge rate, and more than 12 different groups answers\n`INVOICE_TAX_BREAKDOWN_TOO_LONG`, and a tax or surcharge rate with more than two decimals\nanswers `INVOICE_TAX_RATE_TOO_MANY_DECIMALS`. AEAT requires the Macrodato flag on a record of\n100,000,000.00 EUR or more in absolute value; such an invoice, corrective invoices included,\nanswers `INVOICE_TOTAL_TOO_LARGE_FOR_VERIFACTU` and is not numbered.\nThe recipient's `legal_name` is recorded as it is on the invoice,\nso with VeriFactu it must be at most 120 characters: a longer one answers\n`FIELD_TOO_LONG`. BeeL. builds the record it will send before numbering and checks it against AEAT's validations: an\ninvoice with nothing but disbursements (`SUPLIDO` lines) is not an invoice and answers\n`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`, and any other record AEAT would reject answers the specific\ncode or `VERIFACTU_RECORD_NOT_DECLARABLE`, with the AEAT section in its message. In all\nthese cases the invoice is not issued and the number is not used.\n\nOther `error.code` values with this status:\n\n- `CORRECTIVE_INVOICE_INCOMPLETE` — `type: CORRECTIVE` is not accepted when creating an invoice; create correctives from the original with `POST …/invoices/{invoice_id}/corrective`.\n- `DUE_DATE_BEFORE_ISSUE_DATE` — `due_date` is earlier than the issue date (today on create, the stored one on edit); send a due date on or after it.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `LINE_SUPLIDO_MUST_HAVE_NO_IRPF` — a `SUPLIDO` line is outside VAT and cannot carry `irpf_rate`; omit it or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_RECARGO` — a `SUPLIDO` line cannot carry an equivalence surcharge; omit `equivalence_surcharge_rate` or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_TAX` — a `SUPLIDO` line (payment on the client's behalf, art. 78.Tres.3 LIVA) cannot carry a `main_tax` with a non-zero rate; remove it.\n- `LINE_SUPLIDO_REFERENCE_REQUIRED` — a `SUPLIDO` line needs `source_invoice_reference` (or `source_invoice_ids`, from which it is built); say which third-party invoice it passes on.\n- `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.\n- `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`.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PROFORMA_RECIPIENT_REQUIRED` — a proforma needs a recipient: send `customer_id` or the recipient's data.\n- `RECIPIENT_ADDRESS_REQUIRED` — an inline recipient (no `customer_id`) on a non-simplified invoice needs an `address` with at least `street`.\n- `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE` — `customer_id` was sent together with inline recipient fields; send one or the other.\n- `RECIPIENT_FISCAL_NAME_REQUIRED` — an inline recipient (no `customer_id`) on a non-simplified invoice needs a non-blank `legal_name`.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECIPIENT_NOT_PROVIDED` — a standard invoice was sent with an empty `recipient`; send `customer_id` or the recipient's data.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"},"error_simplified_identified_recipient":{"$ref":"#/components/examples/response_example"},"error_derived_unit_price_out_of_range":{"$ref":"#/components/examples/derived_unit_price_out_of_range_response"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}":{"get":{"tags":["Invoices"],"summary":"Get an invoice","description":"Retrieves the full details of an invoice.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/invoices/{invoice_id}`, which\n  behaves identically.\n\n**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.\n","operationId":"getInvoice","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/invoices/{invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"$ref":"#/components/parameters/ActiveCompanyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Invoice retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"get_success":{"$ref":"#/components/examples/get-invoice-success"},"stripe_generated":{"$ref":"#/components/examples/generated-invoice"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["Invoices"],"summary":"Update an invoice","description":"Updates an invoice in `DRAFT` or `SCHEDULED` status, or a proforma in `ACTIVE`.\n\n- **Deprecated:** use `PATCH /v1/companies/{company_id}/invoices/{invoice_id}`, the single\n  update verb of the canonical form.\n- **Difference:** only the verb changes. Both update just the fields present in the body\n  and leave every other one untouched.\n\n**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.\n","operationId":"updateInvoice","x-successor":{"method":"PATCH","path":"/v1/companies/{company_id}/invoices/{invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/ActiveCompanyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInvoiceRequest"},"examples":{"update_invoice":{"$ref":"#/components/examples/update-invoice"}}}}},"responses":{"200":{"description":"Invoice updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"One of:\n- `INVALID_JSON_FORMAT`: the body is not valid JSON, or a property has the wrong type or\n  format. `details` follows `FieldDeserializationError`: `field`, `invalid_value` and, where\n  there is one, `expected_format`.\n- `SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`: a `SIMPLIFIED` invoice whose total, VAT\n  included, is above 3,000€. The same answer when the invoice is created and when an edit\n  takes it over the cap, whether the edit changes the lines or changes `type` to\n  `SIMPLIFIED`. Issue a `STANDARD` invoice with the customer identified instead.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_FOUND` — an id in `source_invoice_ids` does not exist, is in another environment, or is not visible to this account or its managed accounts.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_ISSUED` — a source invoice in `source_invoice_ids` is still a draft (or not in an issued state); issue it first.\n- `LINE_SUPLIDO_TOO_MANY_SOURCES` — more than 200 distinct `source_invoice_ids` in one request; split the suplido across invoices.\n- `LINE_SUPLIDO_TOO_MANY_SOURCE_ACCOUNTS` — the `source_invoice_ids` belong to more than 50 managed accounts; split the request.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECIPIENT_REQUIRED_FOR_ORDINARY` — the edit sent an empty `recipient` on a non-simplified invoice; its recipient can be replaced but not removed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalid_json_format":{"summary":"A property with the wrong format","value":{"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"}}},"error_simplified_exceeds_limit":{"$ref":"#/components/examples/exceeds_3000"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"422":{"description":"Validation error.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nA SIMPLIFIED invoice whose recipient carries a NIF or an alternative identifier is\nrejected with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`, at any amount: issue a\nSTANDARD invoice instead.\n\nOther `error.code` values with this status:\n\n- `DUE_DATE_BEFORE_ISSUE_DATE` — `due_date` is earlier than the issue date (today on create, the stored one on edit); send a due date on or after it.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `LINE_SUPLIDO_MUST_HAVE_NO_IRPF` — a `SUPLIDO` line is outside VAT and cannot carry `irpf_rate`; omit it or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_RECARGO` — a `SUPLIDO` line cannot carry an equivalence surcharge; omit `equivalence_surcharge_rate` or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_TAX` — a `SUPLIDO` line (payment on the client's behalf, art. 78.Tres.3 LIVA) cannot carry a `main_tax` with a non-zero rate; remove it.\n- `LINE_SUPLIDO_REFERENCE_REQUIRED` — a `SUPLIDO` line needs `source_invoice_reference` (or `source_invoice_ids`, from which it is built); say which third-party invoice it passes on.\n- `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.\n- `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`.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `PROFORMA_TYPE_CHANGE_FORBIDDEN` — an edit cannot turn a proforma into an invoice or an invoice into a proforma; use `convert-to-invoice` or create a new document.\n- `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE` — `customer_id` was sent together with inline recipient fields; send one or the other.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `STATUS_NOT_MODIFIABLE` — the invoice's current status does not allow this change: only drafts, scheduled invoices and active proformas can be edited, and only issued, sent or overdue invoices can be marked paid.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `TYPE_CHANGE_CORRECTIVE_FORBIDDEN` — a draft cannot change `type` to or from `CORRECTIVE`; correctives are created from the original with `POST …/corrective`.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"},"error_simplified_identified_recipient":{"$ref":"#/components/examples/response_example"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Invoices"],"summary":"Delete an invoice","description":"Deletes a draft invoice. The record is marked as deleted rather than removed.\n\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/invoices/{invoice_id}`, which\n  behaves identically.\n- **Proformas:** if the draft came from converting a proforma, deleting it returns that\n  proforma to `ACTIVE`.\n\n**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.\n","operationId":"deleteInvoice","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/invoices/{invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/ActiveCompanyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"204":{"description":"Invoice deleted successfully"},"400":{"description":"`STATUS_NOT_DELETABLE`: the invoice cannot be deleted in its current status.\n\nOther `error.code` values with this status:\n\n- `DELETION_FAILED` — the draft was deleted by another request between the read and the delete. Fetch it again: it is most likely already gone.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/bulk/pdf":{"post":{"tags":["InvoiceDelivery"],"summary":"Download multiple invoice PDFs as ZIP","description":"Returns a single ZIP with the PDFs of the requested invoices.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/pdf-archive`, which\n  behaves identically.\n- **Limits:** up to 500 invoices per call, and the ZIP is capped at 50MB.\n- **Missing PDFs:** invoices whose PDF is not available are left out; if none is\n  available the call fails.\n\n**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.\n","operationId":"downloadInvoicesPdfBulk","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/pdf-archive"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invoice_ids"],"properties":{"invoice_ids":{"type":"array","description":"List of invoice IDs to download","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/UUID"}}}},"examples":{"download_5_invoices":{"summary":"Download 5 invoices","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002","550e8400-e29b-41d4-a716-446655440003","550e8400-e29b-41d4-a716-446655440004","550e8400-e29b-41d4-a716-446655440005"]}}}}}},"responses":{"200":{"description":"ZIP file with invoice PDFs","headers":{"X-Bulk-Total":{"description":"Total number of requested invoices","schema":{"type":"integer"}},"X-Bulk-Successful":{"description":"Number of PDFs included in ZIP","schema":{"type":"integer"}},"X-Bulk-Failed":{"description":"Number of PDFs that could not be included","schema":{"type":"integer"}}},"content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"No valid PDFs available or invalid request\n\nOther `error.code` values with this status:\n\n- `NO_PDFS_AVAILABLE` — none of the requested invoices produced a PDF (not found, or issued but PDF not generated yet); no archive is returned. Retry once the PDFs exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error (too many invoices, empty list)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/bulk/send":{"post":{"tags":["InvoiceDelivery"],"summary":"Send multiple invoices by email","description":"Sends one email carrying the PDFs of the requested invoices as attachments.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/deliveries`, which sends\n  the same email from the same body, with `recipients` declared as required there.\n- **Limits:** up to 200 invoices per call, and the attachments are capped at 25MB.\n- **Eligible invoices:** only invoices in `ISSUED`, `SENT`, `PAID` or `OVERDUE` can be\n  sent.\n- **Partial failures:** invoices whose PDF cannot be attached are reported in `failures`\n  and the message is still sent with the rest.\n\n**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.\n","operationId":"sendInvoicesBulkEmail","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/deliveries"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invoice_ids"],"properties":{"invoice_ids":{"type":"array","description":"List of invoice IDs to send","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/UUID"}},"recipients":{"type":"array","description":"Email recipients. At least one is required: no address is inferred from any\nprofile, and a request without recipients is rejected.\n","items":{"$ref":"#/components/schemas/Email"}},"cc":{"type":"array","description":"CC recipients","items":{"$ref":"#/components/schemas/Email"}},"subject":{"type":"string","description":"Custom email subject. If not provided, uses default template.","maxLength":200},"message":{"type":"string","description":"Custom message body. If not provided, uses default template.","maxLength":2000},"language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"description":"Email language. If not provided, uses user's language."}}},"examples":{"send_to_accountant":{"summary":"Send to accountant","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"recipients":["accountant@example.com"],"subject":"Invoices for January 2025","message":"Please find attached the invoices from last month for your review."}},"send_with_cc":{"summary":"Send with CC","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001"],"recipients":["accountant@example.com"],"cc":["copy@example.com"]}}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["email_id","sent_to","sent_at","total_invoices","invoices_attached"],"properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time"},"total_invoices":{"type":"integer","description":"Total number of requested invoices"},"invoices_attached":{"type":"integer","description":"Number of PDFs successfully attached"},"failures":{"type":"array","description":"List of invoices that could not be attached","items":{"type":"object","properties":{"invoice_id":{"$ref":"#/components/schemas/UUID"},"error_code":{"type":"string","enum":["NOT_FOUND","PDF_NOT_GENERATED","INVALID_STATE","INTERNAL_ERROR"]},"error_message":{"type":"string"}}}}}}}}]}}}},"400":{"description":"No valid invoices to send: every selected invoice is a draft or has no generated PDF,\nso there is nothing to attach.\n\nOther `error.code` values with this status:\n\n- `BULK_EMAIL_NO_VALID_INVOICES` — none of the requested invoices could be attached (not found, draft or scheduled, or PDF not generated yet); nothing was sent. Fix them and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error: too many invoices, invalid recipients, or no recipients specified.\n`recipients` has no fallback — a request without recipients is rejected here with\n`INVOICE_EMAIL_NO_RECIPIENTS`, the same code as a single send; it is not completed\nfrom any profile or customer.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/bulk/status":{"post":{"tags":["InvoiceDelivery"],"summary":"Change the status of several invoices","description":"Changes the status of several invoices at once and reports, invoice by invoice, which\nsucceeded and which failed.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/batches` with\n  `operation: STATUS`, which applies the same change. `new_status` and `payment_date`\n  travel in the same body there.\n- **Target status:** `new_status` accepts `SENT` or `PAID`. `PAID` requires\n  `payment_date`.\n- **Limits:** up to 50 invoices per call.\n\n**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.\n","operationId":"changeInvoicesStatusBulk","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/batches"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invoice_ids","new_status"],"properties":{"invoice_ids":{"type":"array","description":"List of invoice IDs to update","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/UUID"}},"new_status":{"type":"string","description":"Target status for the invoices","enum":["SENT","PAID"]},"payment_date":{"type":"string","format":"date","description":"Payment date (required when new_status is PAID)","example":"2025-01-15"}}},"examples":{"mark_as_sent":{"summary":"Mark invoices as sent","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"new_status":"SENT"}},"mark_as_paid":{"summary":"Mark invoices as paid","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"new_status":"PAID","payment_date":"2025-01-15"}}}}}},"responses":{"200":{"description":"Status change operation completed (may include partial failures)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"400":{"description":"Invalid request (empty list, missing payment_date for PAID)\n\nOther `error.code` values with this status:\n\n- `PAYMENT_DATE_REQUIRED` — `new_status: PAID` was sent without `payment_date`; add the date the invoices were paid.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error (unsupported status, too many invoices)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/bulk/issue":{"post":{"tags":["InvoiceDelivery"],"summary":"Issue several draft invoices","description":"Issues several draft invoices at once, each one assigned its definitive number.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/batches` with\n  `operation: ISSUE`, which issues the same drafts.\n- **Limits:** up to 50 invoices per call.\n- **Not atomic:** each invoice is issued in its own transaction, and since issuing is\n  irreversible, the ones already issued stay issued if a later one fails.\n\n**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.\n","operationId":"issueInvoicesBulk","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/batches"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["invoice_ids"],"properties":{"invoice_ids":{"type":"array","description":"Draft invoice IDs to issue","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/UUID"}}}},"examples":{"issue_drafts":{"summary":"Issue reviewed drafts","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"]}}}}}},"responses":{"200":{"description":"Issue operation completed (may include partial failures)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"400":{"description":"Invalid request (empty list)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error (too many invoices)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/pdf":{"get":{"tags":["InvoiceDelivery"],"summary":"Get invoice PDF download URL","description":"Returns a temporary pre-signed URL to download the invoice PDF.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/invoices/{invoice_id}/pdf`, which\n  behaves identically.\n- **URL:** it expires in five minutes and only allows `GET`.\n- **Waiting:** a PDF is produced asynchronously, so this request **waits** for it (up to\n  ten seconds) instead of handing you a polling loop to write. Bound the wait with\n  `Prefer: wait=N`, or opt out with `Prefer: wait=0`.\n- **Never modified:** the PDF of an issued invoice is generated once and does not change\n  when the invoice is voided or corrected.\n\n**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.\n","operationId":"generateInvoicePdf","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/invoices/{invoice_id}/pdf"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}},{"name":"Prefer","in":"header","required":false,"description":"RFC 7240 preference bounding how long this request may wait for a PDF that is still\nbeing generated: `Prefer: wait=N`, with `N` in seconds.\n\nBy default the request waits (up to the server cap) and answers `200` with the URL, so\nthe `202` is the exception rather than the normal path. Use `Prefer: wait=0` to opt out\nand get the old poll-only behaviour: an immediate `202` while the PDF is in flight.\n\nA value above the cap is lowered to it, and the `202` then echoes what was actually\napplied in `Preference-Applied: wait=<seconds>` — so you never have to discover the cap\nby trial and error. A value that is not a non-negative integer is ignored altogether\n(RFC 7240: a preference that is not understood is not an error), and the request falls\nback to the default wait with no `Preference-Applied` header.\n","schema":{"type":"string"},"example":"wait=5"}],"responses":{"200":{"description":"Download URL generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicePdfResponse"}}}},"202":{"description":"The PDF was still not ready when the wait window elapsed, so no body is returned.\nUncommon: the request waits for the PDF by default and normally answers `200`. It is\nwhat you always get with `Prefer: wait=0`. Ask again — `Retry-After` says how soon.\n","headers":{"Retry-After":{"description":"Seconds to wait before asking again.","schema":{"type":"integer"}},"Preference-Applied":{"description":"Echoes the wait actually applied (`wait=<seconds>`) when the request sent a\n`Prefer: wait=N` that was honoured — lower than asked if it exceeded the cap.\n","schema":{"type":"string"}}}},"400":{"description":"`INVOICE_NOT_ISSUED_NO_PDF` — the invoice is a draft: it has no fiscal PDF and no emission is generating one. Issue it, or use the preview endpoint. Also covers the invalid request target. `INVOICE_NOT_REGISTERED_NO_PDF` — the invoice is not registered with the AEAT, so there is no QR code to print and no PDF: its registration was rejected before reaching the AEAT, or it was voided without being registered. Both are answered immediately: what will never have a PDF does not wait.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/pdf/preview":{"get":{"tags":["InvoiceDelivery"],"summary":"Preview the PDF of a draft invoice","description":"Renders the PDF of a draft invoice on the fly, without storing it, and returns it as\n`application/pdf`.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/invoices/{invoice_id}/pdf/preview`,\n  which behaves identically.\n\n**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.\n","operationId":"previewDraftInvoicePdf","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/invoices/{invoice_id}/pdf/preview"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID (must be a draft)","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"PDF preview generated successfully","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Invoice is not in draft status\n\nOther `error.code` values with this status:\n\n- `PREVIEW_DRAFT_ONLY` — the invoice is already issued (or voided); preview only works on drafts, scheduled invoices and proformas. Download its real PDF instead.\n- `PREVIEW_GENERATION_ERROR` — the preview PDF could not be rendered. Nothing changed on the invoice; retry later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/send":{"post":{"tags":["InvoiceDelivery"],"summary":"Send an invoice by email","description":"Sends the invoice by email with its PDF attached.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/{invoice_id}/send`, which\n  behaves identically.\n- **Recipients:** the addresses configured for the customer are used unless the body\n  overrides them with `recipients`.\n\n**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.\n","operationId":"sendInvoiceEmail","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/{invoice_id}/send"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time"}}}}}]}}}},"202":{"description":"Accepted, not sent yet: `attach_pdf` is `true` and the invoice's PDF is not generated\nyet. This can happen right after issuing, whether or not the invoice goes through\nVeriFactu — the PDF is generated asynchronously — or, for a VeriFactu invoice\nspecifically, while it waits for the QR of its registration. The email goes out\nshortly after the PDF is stored, with it attached (it can take several minutes if\nVeriFactu or PDF generation is delayed). Follow it with `email_id`. If the invoice ends\nup with no PDF (a VeriFactu registration that ends without a QR, or PDF generation that\nfails for good), or its PDF is not stored within a day, the email is marked as failed,\nwith its reason.\n\n`sent_at` here is when the request was ACCEPTED, not when the email actually went out\n— it has not, yet. The `invoice.email.sent` webhook tells you when it goes out; a\nfailed email has no webhook, so check its status by `email_id`.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time","description":"The moment this request was accepted, not the moment the email was\nsent — it has not been sent yet. See the `202` description.\n"}}}}}]}}}},"400":{"description":"The invoice cannot be sent: `INVOICE_DRAFT_NOT_SENDABLE` when it is not issued yet, a\ndraft or a scheduled invoice (issue it first), `INVOICE_NOT_REGISTERED_NO_PDF` when `attach_pdf` is `true` and the invoice has\nno VeriFactu registration, so it has no PDF (the reason is in\n`verifactu.error_message`).\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICES_REQUIRES_PDF` — `attach_source_invoices: true` was sent with `attach_pdf: false`; the source-invoice ZIP only travels alongside the invoice PDF. Set `attach_pdf: true`.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"`INVOICE_EMAIL_NO_RECIPIENTS`: the request sends no recipients and the recipient of the\ninvoice has no email address.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/mark-paid":{"post":{"tags":["InvoiceLifecycle"],"summary":"Mark an invoice as paid","description":"Marks an invoice as paid.\n\n- **Deprecated:** use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/status` with\n  `{\"status\": \"PAID\"}`, which behaves identically.\n- **Payment details:** `payment_date` and `payment_method` travel in the same body\n  there.\n\n**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.\n","operationId":"markInvoicePaid","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/invoices/{invoice_id}/status"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"payment_date":{"type":"string","format":"date","description":"Payment date (defaults to today)","example":"2025-01-15"},"payment_method":{"$ref":"#/components/schemas/PaymentInfo","description":"Payment details object (not a string enum). Example:\n`{ \"method\": \"BANK_TRANSFER\", \"iban\": \"ES9121000418450200051332\" }`\n"}}}}}},"responses":{"200":{"description":"Invoice marked as paid successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Cannot mark as paid (invalid status)\n\nOther `error.code` values with this status:\n\n- `STATUS_NOT_MODIFIABLE` — the invoice's current status does not allow this change: only drafts, scheduled invoices and active proformas can be edited, and only issued, sent or overdue invoices can be marked paid.\n\nOther `error.code` values with this status:\n\n- `PROFORMA_PAYMENT_FORBIDDEN` — the document is a proforma, which is not a fiscal document and never takes a payment; convert it with `convert-to-invoice` and mark that invoice paid.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/void":{"post":{"tags":["InvoiceLifecycle"],"summary":"Void an invoice","description":"Voids an issued invoice, moving it to `VOIDED`.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/{invoice_id}/void`, which\n  behaves identically.\n- **VeriFactu:** when it is enabled for the invoice, the cancellation is submitted to the\n  AEAT.\n- **`reason`:** required in the body. `void_date` is optional.\n- **Sent, paid or corrected invoices:** the successor operation documents when they can be\n  voided (`issued_in_error`, `422 VOID_REQUIRES_ISSUED_IN_ERROR`,\n  `422 INVOICE_HAS_LIVE_CORRECTIVES`, `422 TOTAL_CORRECTIVE_NOT_VOIDABLE`).\n- **PDF:** unchanged; the void is reported by the invoice's `status`.\n\n**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.\n","operationId":"voidInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/{invoice_id}/void"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInvoiceRequest"},"examples":{"void_by_error":{"$ref":"#/components/examples/void-issued-by-error"}}}}},"responses":{"200":{"description":"Invoice voided successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Cannot void invoice: the transition is not supported from its current status\n(`TRANSITION_NOT_SUPPORTED`), or the request data is invalid.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The invoice is already `VOIDED` (`INVOICE_ALREADY_VOIDED`). Voiding is not\nrepeatable: treat this as a retry of a request that already landed, not as a\nnew failure.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `EXCHANGE_INVOICE_NOT_VOIDABLE` — the invoice replaced simplified invoices in an exchange; voiding it would drop those sales from the books. Correct it instead.\n- `INVOICE_HAS_LIVE_CORRECTIVES` — the invoice has live correctives, so the operation took place and cannot be voided; issue another corrective instead.\n- `TOTAL_CORRECTIVE_NOT_VOIDABLE` — a TOTAL corrective cannot be voided: its original would stay voided with nothing offsetting it. Issue a new invoice instead.\n- `VOID_DATE_BEFORE_ISSUE_DATE` — `void_date` is earlier than the invoice's issue date; an invoice cannot be voided before it exists.\n- `VOID_REQUIRES_ISSUED_IN_ERROR` — the invoice was already emailed to the customer or paid, whatever its current status; set `issued_in_error: true` to confirm it was issued by mistake, or correct it instead.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/corrective":{"post":{"tags":["InvoiceLifecycle"],"summary":"Create a corrective invoice","description":"Issues a corrective invoice that amends an issued invoice.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/{invoice_id}/corrective`,\n  which behaves identically.\n- **Rectification type:** a `TOTAL` rectification rectifies what is still invoiced on the\n  original, its live correctives included, and leaves it `VOIDED`; a `PARTIAL` one leaves\n  it `RECTIFIED` and may not take the base of any rate below zero\n  (`422 CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`).\n- **Fiscal inheritance on a `PARTIAL`:** lines that omit `irpf_rate` or\n  `equivalence_surcharge_rate` take them from the original invoice, not from the\n  company's current tax profile; an ambiguous original fails with\n  `422 CORRECTIVE_ORIGINAL_MIXED_IRPF` or `422 CORRECTIVE_ORIGINAL_MIXED_SURCHARGE`.\n  The successor operation documents the rule in full.\n\n**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.\n","operationId":"createCorrectiveInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/{invoice_id}/corrective"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID to rectify","schema":{"$ref":"#/components/schemas/UUID"}},{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCorrectiveInvoiceRequest"},"examples":{"corrective_total_r1":{"$ref":"#/components/examples/corrective-total-r1"},"corrective_total_cancellation_r1":{"$ref":"#/components/examples/corrective-total-cancellation-r1"},"corrective_partial_r2":{"$ref":"#/components/examples/corrective-partial-r2"},"corrective_partial_r3":{"$ref":"#/components/examples/corrective-partial-r3"},"corrective_r1_partial_discount":{"$ref":"#/components/examples/r1-partial-discount"},"corrective_recipient_data_r4":{"$ref":"#/components/examples/recipient-data-r4"},"corrective_r5_partial_simplified_return":{"$ref":"#/components/examples/r5-partial-simplified-return"}}}}},"responses":{"201":{"description":"Corrective invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Invalid rectification data\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Original invoice not found\n\nOther `error.code` values with this status:\n\n- `CORRECTIVE_ORIGINAL_NOT_FOUND` — the invoice in the path does not exist, was deleted, or belongs to another company or environment than the request.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Original invoice cannot be rectified (invalid status) — `error.code` is\n`INVOICE_NOT_CORRECTIBLE_IN_CURRENT_STATUS`. This is also what a **second TOTAL\ncorrective** gets: the first one left the original `VOIDED`, and a `VOIDED` invoice is\nnot rectifiable. There is no dedicated `409` for it; to retry the same call safely,\nsend an `Idempotency-Key`. Or the company/NIF is not ready\nto issue in this environment — `error.code` is `EMISSION_NOT_READY` and\n`error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`, `NIF_NOT_REGISTERED`,\n`NIF_REPRESENTATION_REQUIRED` — the last one only in production, where the operation\nreaches the real tax authority); when a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use. Or the explicit `series_id` is a series of the wrong\ntype — `error.code` is `SERIES_INCOMPATIBLE_DOC_TYPE` and\n`error.details.expected_document_type` says which type was required. When `series_id`\nis omitted and the company has no default corrective series, one is created on first\nuse (code `R`, or the next free one).\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAt issue time the generated number must also fit the AEAT invoice number: more than 60\ncharacters answers `INVOICE_NUMBER_TOO_LONG`, a character outside printable ASCII or one\nof `\"`, `'`, `<`, `>`, `=` answers `INVOICE_NUMBER_INVALID_CHARACTERS`. In both cases the\ninvoice is not issued and the number is not used; issue it with another series, or fix the\nseries code or format while the series has no issued invoices, and retry.\nWhen the invoice is recorded with VeriFactu, its tax breakdown must also fit one AEAT\nrecord: lines are grouped by tax, regime key, operation type, exemption, tax rate and\nequivalence surcharge rate, and more than 12 different groups answers\n`INVOICE_TAX_BREAKDOWN_TOO_LONG`, and a tax or surcharge rate with more than two decimals\nanswers `INVOICE_TAX_RATE_TOO_MANY_DECIMALS`. AEAT requires the Macrodato flag on a record of\n100,000,000.00 EUR or more in absolute value; such an invoice, corrective invoices included,\nanswers `INVOICE_TOTAL_TOO_LARGE_FOR_VERIFACTU` and is not numbered.\nThe recipient's `legal_name` is recorded as it is on the invoice,\nso with VeriFactu it must be at most 120 characters: a longer one answers\n`FIELD_TOO_LONG`. BeeL. builds the record it will send before numbering and checks it against AEAT's validations: an\ninvoice with nothing but disbursements (`SUPLIDO` lines) is not an invoice and answers\n`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`, and any other record AEAT would reject answers the specific\ncode or `VERIFACTU_RECORD_NOT_DECLARABLE`, with the AEAT section in its message. In all\nthese cases the invoice is not issued and the number is not used.\n\nOther `error.code` values with this status:\n\n- `CORRECTIVE_BAD_DEBT_BASE_TOO_LOW` — an `R3` (bad debt) on an operation with a taxable base of 50 € or less needs `recipient_is_business: true`; without it the reduction is not allowed.\n- `CORRECTIVE_BAD_DEBT_TOO_EARLY` — an `R3` (bad debt) is not allowed until six months after the operation date; retry from `details.earliest_date`.\n- `CORRECTIVE_CIRCUMSTANCE_DATE_NOT_APPLICABLE` — `circumstance_date` was sent with `R4`, which is not an article 80 cause; drop the field or use the right code.\n- `CORRECTIVE_CIRCUMSTANCE_DATE_OUT_OF_RANGE` — `circumstance_date` is before the original's operation date or in the future; send a date between the two.\n- `CORRECTIVE_EXCEEDS_INVOICED_AMOUNT` — the PARTIAL lines take a tax rate's base below zero; `details.max_reduction` is what can still be reduced for `details.tax_group`.\n- `CORRECTIVE_NOTHING_LEFT_TO_RECTIFY` — earlier correctives already brought the original's balance to zero, so a TOTAL corrective (or a recipient-data correction) has nothing left to rectify.\n- `CORRECTIVE_NOT_RECTIFIABLE` — the target is itself a corrective invoice; correct the original standard or simplified invoice with a new corrective instead.\n- `CORRECTIVE_ORIGINAL_RECORD_REJECTED` — the AEAT rejected the original's registration and it has not been fixed; resubmit or correct that record before issuing a corrective.\n- `CORRECTIVE_PARTIAL_SUPLIDO_NOT_ALLOWED` — a PARTIAL line has `line_type: SUPLIDO`; a disbursement is outside the taxable base and is not rectified. If the amount was not a disbursement, send it as a `NORMAL` line with its tax.\n- `CORRECTIVE_OUT_OF_TIME` — more than four years have passed since the operation date or `circumstance_date` (`details.counted_from`); the last valid day was `details.deadline`.\n- `CORRECTIVE_RECENT_DUPLICATE` — an identical corrective of this invoice was issued under two minutes ago; retry with the same `Idempotency-Key` instead of resending.\n- `CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON` — the corrected `recipient` is a different customer; void with a TOTAL corrective and issue a new invoice to the right one.\n- `CORRECTIVE_RECIPIENT_NOT_ACCEPTED` — `recipient` is only accepted in a PARTIAL `R4` without `lines` (recipient-data correction); omit it otherwise and the original's recipient is used.\n- `CORRECTIVE_RECIPIENT_NOT_ESTABLISHED` — `R2`/`R3` require a recipient established in Spain (`R2` also accepts one in the EU); use another code if that is not the case.\n- `CORRECTIVE_RECIPIENT_UNCHANGED` — the corrected `recipient` has the same name, tax ID and address as recorded, so there is nothing to correct.\n- `CORRECTIVE_WITHHOLDING_ONLY` — the PARTIAL lines only change the withholding (IRPF), which is not a cause for a corrective; void the invoice and issue a new one.\n- `EXCHANGE_INVOICE_NOT_CORRECTABLE` — the invoice replaced simplified invoices and was registered as F3; it cannot be corrected with `R1`–`R4`.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `PROFORMA_CORRECTIVE_FORBIDDEN` — a proforma is not a fiscal document and cannot be corrected; void it or edit it while active.\n- `RECTIFICATION_REASON_REQUIRED` — `reason` is blank (only whitespace); explain why the invoice is being corrected.\n- `RECTIFICATIVA_PARCIAL_SIN_LINEAS` — a PARTIAL corrective needs `lines` with the adjustment, unless it only corrects the recipient's data (`R4` with `recipient`).\n- `RECTIFICATIVA_TOTAL_CON_LINEAS` — a TOTAL corrective takes no `lines`: it negates what is still invoiced on the original. Remove `lines` or use PARTIAL.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"incompatible_series":{"$ref":"#/components/examples/incompatible_series"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/issue":{"post":{"tags":["InvoiceLifecycle"],"summary":"Issue an invoice","description":"Finalizes a draft invoice: assigns its definitive number from the configured series and\nmakes it immutable. Irreversible.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/{invoice_id}/issue`, which\n  behaves identically.\n- **PDF:** the PDF is generated asynchronously unless `wait_for_pdf` is `true`, which\n  waits for it and returns its URL.\n\n**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.\n","operationId":"issueInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/{invoice_id}/issue"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}},{"name":"wait_for_pdf","in":"query","required":false,"description":"If `true`, waits for PDF generation and returns the URL in the response.\nAdds ~1-2s latency but guarantees PDF is immediately available.\nIf `false` (default), PDF is generated asynchronously.\n","schema":{"type":"boolean","default":false}},{"name":"attach_source_invoices","in":"query","required":false,"description":"Only applies when the invoice has automatic email sending enabled. If `true`, the\nemail sent after issuing also attaches a ZIP (`suplidos_<invoice-number>.zip`) with\nthe PDFs of the source invoices referenced by the invoice's SUPLIDO consolidation\nlines. Access to sources owned by managed accounts is re-checked with the same rules\nas issuing, and the request fails synchronously with an actionable error — never a\npartial ZIP — if the invoice has no consolidation sources\n(`ATTACH_SOURCE_INVOICES_NO_SOURCES`), a source is not reachable\n(`ATTACH_SOURCE_INVOICE_UNAVAILABLE`) or a source has no generated PDF\n(`ATTACH_SOURCE_PDF_MISSING`).\n","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Invoice issued successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Cannot issue invoice (invalid status or incomplete data)\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Invoice cannot be issued (validation failed), or the company/NIF is not ready to issue\nin this environment. In the latter case `error.code` is `EMISSION_NOT_READY` and\n`error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`, `NIF_NOT_REGISTERED`,\n`NIF_REPRESENTATION_REQUIRED`). When a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use. Sandbox applies the same gate as production, except\n`NIF_REPRESENTATION_REQUIRED`: the signed representation is only required when the\noperation reaches the real tax authority.\nAt issue time the generated number must also fit the AEAT invoice number: more than 60\ncharacters answers `INVOICE_NUMBER_TOO_LONG`, a character outside printable ASCII or one\nof `\"`, `'`, `<`, `>`, `=` answers `INVOICE_NUMBER_INVALID_CHARACTERS`. In both cases the\ninvoice is not issued and the number is not used; issue it with another series, or fix the\nseries code or format while the series has no issued invoices, and retry.\nWhen the invoice is recorded with VeriFactu, its tax breakdown must also fit one AEAT\nrecord: lines are grouped by tax, regime key, operation type, exemption, tax rate and\nequivalence surcharge rate, and more than 12 different groups answers\n`INVOICE_TAX_BREAKDOWN_TOO_LONG`, and a tax or surcharge rate with more than two decimals\nanswers `INVOICE_TAX_RATE_TOO_MANY_DECIMALS`. AEAT requires the Macrodato flag on a record of\n100,000,000.00 EUR or more in absolute value; such an invoice, corrective invoices included,\nanswers `INVOICE_TOTAL_TOO_LARGE_FOR_VERIFACTU` and is not numbered.\nThe recipient's `legal_name` is recorded as it is on the invoice,\nso with VeriFactu it must be at most 120 characters: a longer one answers\n`FIELD_TOO_LONG`. BeeL. builds the record it will send before numbering and checks it against AEAT's validations: an\ninvoice with nothing but disbursements (`SUPLIDO` lines) is not an invoice and answers\n`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`, and any other record AEAT would reject answers the specific\ncode or `VERIFACTU_RECORD_NOT_DECLARABLE`, with the AEAT section in its message. In all\nthese cases the invoice is not issued and the number is not used.\nA draft that still stores the 0.625 equivalence surcharge (0.62 since Royal Decree-law\n1/2023) answers `SURCHARGE_RATE_REQUIRES_RECALCULATION` and is not issued: edit the\ndraft so its lines are recalculated, and retry.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `ONLY_DRAFT_EMITTABLE` — only a `DRAFT` can be issued; this invoice is already issued or in another state. Read it to see its number.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PROFORMA_NOT_ISSUABLE` — a proforma is never issued; convert it with `convert-to-invoice` (optionally with `issue: true`).\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/convert-to-invoice":{"post":{"tags":["Proforma"],"summary":"Convert a proforma into an invoice","description":"Converts an accepted proforma into a real invoice.\n\n- **Deprecated:** use\n  `POST /v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice`, which\n  behaves identically.\n- **Result:** the proforma moves to the terminal status `CONVERTED` and a new `STANDARD`\n  invoice is created in `DRAFT`.\n- **Issuing:** `issue: true` numbers and issues that new invoice in the same call.\n\n**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.\n","operationId":"convertProformaToInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Proforma ID to convert","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertProformaToInvoiceRequest"}}}},"responses":{"201":{"description":"Invoice created successfully from the proforma (draft, or emitted when `issue` is true)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Branch on `error.code`:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The proforma has already been converted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Document is not a proforma, or its status does not allow conversion, or the company/NIF\nis not ready to issue in this environment. In the latter case `error.code` is\n`EMISSION_NOT_READY` and `error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`,\n`NIF_NOT_REGISTERED`, `NIF_REPRESENTATION_REQUIRED`). When a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/mark-sent":{"post":{"tags":["InvoiceLifecycle"],"summary":"Mark an invoice as sent","description":"Marks an issued invoice as sent through a channel outside the email system, recording\n`sent_at`.\n\n- **Deprecated:** use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/status` with\n  `{\"status\": \"SENT\"}`, which behaves identically.\n- **Timestamp:** the moment of sending travels in the same body there, as `sent_at`.\n\n**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.\n","operationId":"markInvoiceSent","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/invoices/{invoice_id}/status"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"sent_at":{"type":"string","format":"date-time","description":"Custom timestamp for when the invoice was sent.\nIf not provided, the current time will be used.\n","example":"2025-01-29T18:45:00Z"}}}}}},"responses":{"200":{"description":"Invoice marked as sent successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Cannot mark as sent (invalid status - must be ISSUED)\n\nOther `error.code` values with this status:\n\n- `MARK_SENT_ALREADY_SENT` — the invoice is already `SENT`; nothing to do.\n- `MARK_SENT_FROM_DRAFT_NOT_ALLOWED` — the invoice is still a `DRAFT`; issue it first, then mark it as sent.\n- `MARK_SENT_FROM_INVALID_STATE` — only an `ISSUED` invoice can be marked as sent; this one is scheduled, or is a proforma (active or already invoiced).\n- `MARK_SENT_FROM_LATER_STATE` — the invoice is already past `SENT` (paid, overdue, voided or corrected); its status cannot go back.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Branch on `error.code`:\n\n- `PROFORMA_TRANSITION_FORBIDDEN` — the document is a proforma, which only moves between active, invoiced and voided; convert it with `convert-to-invoice` instead of changing its status.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/revert-to-issued":{"post":{"tags":["InvoiceLifecycle"],"summary":"Revert an invoice to issued","description":"Returns an invoice from `SENT` to `ISSUED` and clears `sent_at`, to undo a `SENT` set by\nmistake. It never un-issues an invoice.\n\n- **Deprecated:** use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/status` with\n  `{\"status\": \"ISSUED\"}`, which behaves identically.\n\n**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.\n","operationId":"revertInvoiceToIssued","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/invoices/{invoice_id}/status"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Invoice reverted to ISSUED successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"`REVERT_ONLY_FROM_SENT`: only an invoice in `SENT` status can be reverted to `ISSUED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/schedule":{"post":{"tags":["InvoiceLifecycle"],"summary":"Schedule an invoice","description":"Schedules a draft invoice to be processed on a future date, moving it to `SCHEDULED`.\n\n- **Deprecated:** use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`,\n  which schedules the same way.\n- **Difference:** the `action` flag is replaced there by `generation_mode`, which is\n  required.\n\n**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.\n","operationId":"scheduleInvoice","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/invoices/{invoice_id}/schedule"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_for"],"properties":{"scheduled_for":{"type":"string","format":"date","description":"Date when the invoice should be processed (must be today or future)","example":"2025-02-15"},"action":{"$ref":"#/components/schemas/GenerationAction"}}}}}},"responses":{"200":{"description":"Invoice scheduled successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"The invoice cannot be scheduled from its current status — it is already issued, voided\nor scheduled (`INVOICE_STATUS_NOT_SCHEDULABLE`), the same code the canonical\n`PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule` answers. A proforma is\nrejected earlier, by type, with `PROFORMA_SCHEDULE_FORBIDDEN`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"The request is well formed but cannot be processed. Returned with\n`SCHEDULED_DATE_IN_PAST` when `scheduled_for` is earlier than today; the rejected\nvalue is echoed in `error.details.scheduled_for`.\n\nOther `error.code` values with this status:\n\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/unschedule":{"post":{"tags":["InvoiceLifecycle"],"summary":"Cancel the scheduling of an invoice","description":"Removes the scheduling of an invoice, returning it to `DRAFT`.\n\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/invoices/{invoice_id}/schedule`,\n  which removes the same scheduling.\n- **Difference:** that route answers `204` with no body, where this one answers `200` with\n  the invoice.\n\n**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.\n","operationId":"unscheduleInvoice","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/invoices/{invoice_id}/schedule"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Invoice unscheduled successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"The invoice is not scheduled, so there is nothing to remove (`INVOICE_NOT_SCHEDULED`).\nThis is what a repeated call answers, since the first one already left the invoice as a\ndraft: treat it as a retry of a request that landed, not as a new failure. The canonical\n`DELETE /v1/companies/{company_id}/invoices/{invoice_id}/schedule` is idempotent and\nanswers `204` in that case.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/reschedule":{"patch":{"tags":["InvoiceLifecycle"],"summary":"Reschedule an invoice","description":"Changes the date of a scheduled invoice, which stays `SCHEDULED`. The new date must be\ntoday or later.\n\n- **Deprecated:** use `PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`, the\n  single verb that schedules and reschedules.\n- **Difference:** the new date travels there in `scheduled_for`, alongside the required\n  `generation_mode`.\n\n**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.\n","operationId":"rescheduleInvoice","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/invoices/{invoice_id}/schedule"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["scheduled_for"],"properties":{"scheduled_for":{"type":"string","format":"date","description":"New date when the invoice should be processed","example":"2025-03-01"}}}}}},"responses":{"200":{"description":"Invoice rescheduled successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"The invoice is not scheduled, so there is no scheduling to move\n(`INVOICE_NOT_SCHEDULED`). A proforma is rejected by type with\n`PROFORMA_SCHEDULE_FORBIDDEN`. A `scheduled_for` in the past is a `422`, not a `400`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/duplicate":{"post":{"tags":["Invoices"],"summary":"Duplicate an invoice as a draft","description":"Creates a copy of an existing invoice as a new draft.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/derivations`, which\n  creates the same draft.\n- **Difference:** the source invoice travels there in the body as `from_invoice_id`\n  instead of in the path, and `mode` is required.\n- **What is copied:** recipient, lines, payment method, series and observations. Number,\n  status, dates, VeriFactu data and PDF are reset.\n\n**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.\n","operationId":"duplicateInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/derivations"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"description":"ID of the invoice to duplicate","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"series_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Series ID for the new invoice.\nIf not provided, uses the same series as the original.\n"},"notes":{"type":"string","maxLength":2000,"description":"Observations for the new invoice.\nIf not provided, copies from the original invoice.\n"}}},"examples":{"simple_duplicate":{"summary":"Simple duplication (defaults)","value":{}},"duplicate_other_series":{"summary":"Duplication to different series","value":{"series_id":"550e8400-e29b-41d4-a716-446655440099"}}}}}},"responses":{"201":{"description":"Invoice duplicated successfully as draft","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"Cannot duplicate invoice (e.g., invoice is deleted)\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Original invoice not found\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The series is not compatible with the type of the copy\n(`SERIES_INCOMPATIBLE_DOC_TYPE`), or the account has no default series\nfor that type (`SERIES_DEFAULT_NOT_FOUND`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `LINE_NEGATIVE_PRICE` — a non-corrective document cannot have a negative unit price (e.g. duplicating a corrective); use a negative quantity or a corrective invoice.\n- `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`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/export/excel":{"post":{"tags":["InvoiceDelivery"],"summary":"Export invoices to a spreadsheet","description":"Exports invoices to an XLSX file.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/invoices/exports`, which produces\n  the same file.\n- **Difference:** the selection criteria are grouped there under `filters` instead of\n  being top-level fields.\n- **Selection:** the invoices named in `invoice_ids`, or, when that list is absent, the\n  ones matching the filters.\n- **Format:** `SUMMARY` gives one row per invoice with aggregated totals, `ITEMS` one row\n  per invoice line.\n\n**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.\n","operationId":"exportInvoicesExcel","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/invoices/exports"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"invoice_ids":{"type":"array","description":"List of specific invoice IDs to export.\nIf provided, filters are ignored.\n","items":{"$ref":"#/components/schemas/UUID"}},"format":{"type":"string","description":"Export format.\n- **SUMMARY**: One row per invoice with totals (default)\n- **ITEMS**: One row per invoice line item\n","enum":["SUMMARY","ITEMS"],"default":"SUMMARY"},"status":{"$ref":"#/components/schemas/InvoiceStatus"},"type":{"$ref":"#/components/schemas/InvoiceType"},"date_from":{"type":"string","format":"date","description":"Issue date from (YYYY-MM-DD)"},"date_to":{"type":"string","format":"date","description":"Issue date to (YYYY-MM-DD)"},"customer_id":{"$ref":"#/components/schemas/UUID"},"recipient_name":{"type":"string","description":"Filter by recipient name (partial match)"},"recipient_nif":{"type":"string","description":"Filter by recipient NIF (partial match)"},"series_code":{"type":"string","description":"Filter by series code"}}},"examples":{"export_by_ids":{"summary":"Export specific invoices (default format)","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002","550e8400-e29b-41d4-a716-446655440003"]}},"export_items_format":{"summary":"Export as items (one row per line)","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001"],"format":"ITEMS"}},"export_q1_2025":{"summary":"Export Q1 2025 invoices","value":{"date_from":"2025-01-01","date_to":"2025-03-31"}},"export_paid":{"summary":"Export all paid invoices","value":{"status":"PAID"}}}}}},"responses":{"200":{"description":"Excel file generated successfully","headers":{"Content-Disposition":{"description":"Suggested filename for download","schema":{"type":"string"},"example":"attachment; filename=\"facturas_2025-01-15.xlsx\""},"X-Total-Invoices":{"description":"Number of invoices included in the export","schema":{"type":"integer"}}},"content":{"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"`EXPORT_SELECTION_REQUIRED` — the request names no invoices: neither a non-empty `invoice_ids` nor a single filter. An export with no criteria is rejected, never answered with the whole account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error. `EXPORT_LIMIT_EXCEEDED` — the selection (by `invoice_ids` or by `filters`) matches more invoices than the per-export maximum. Narrow the date range or split the selection: the export is never silently truncated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyInvoices","tags":["CompanyInvoices"],"summary":"List invoices","description":"Returns a paginated list of the invoices of this company, filterable by status, type,\nseries, customer, date range and free text. Only the documents of the company in the path\nare returned.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"search","in":"query","description":"Global search across invoice number, recipient name, recipient NIF, and series code (partial, case-insensitive)","schema":{"type":"string"}},{"name":"status","in":"query","description":"Filter by invoice status. Accepts a comma-separated list to match any of several\nstatuses, for example `status=DRAFT,ISSUED`. A single value is also valid. An empty\nvalue (`status=`) is the same as omitting the parameter.\n","schema":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceStatus"}},"style":"form","explode":false},{"name":"payment_method","in":"query","description":"Filter by payment method. Accepts a comma-separated list to match any of several\nmethods, for example `payment_method=DIRECT_DEBIT,CARD`. A single value is also valid.\n`NONE` also matches invoices that have no payment method stored. Combine it with\n`date_from`/`date_to` to list, for example, the direct debits of a month. An empty\nvalue (`payment_method=`) is the same as omitting the parameter.\n","schema":{"type":"array","items":{"$ref":"#/components/schemas/PaymentMethod"}},"style":"form","explode":false},{"name":"type","in":"query","description":"Filter by invoice type","schema":{"$ref":"#/components/schemas/InvoiceType"}},{"name":"fiscal_only","in":"query","description":"When `true` (the default), returns only fiscal documents (STANDARD, CORRECTIVE,\nSIMPLIFIED), excluding proformas and any other non-fiscal document. Set it to `false`\nto list every document type. Ignored when an explicit `type` is given: use\n`type=PROFORMA` to list proformas.\n","schema":{"type":"boolean","default":true}},{"name":"pending_collection_only","in":"query","description":"When `true`, returns only the invoices pending collection: STANDARD, SIMPLIFIED or\nCORRECTIVE invoices in status ISSUED, SENT, OVERDUE or RECTIFIED with no payment recorded\n(no `payment_date`). A CORRECTIVE invoice whose original invoice is `VOIDED` with no\n`payment_date` is left out: nothing is owed and nothing has to be refunded for that\npair. One whose original has a `payment_date` is included. It combines with the other\nfilters, so a `status` or `type` outside that set returns an empty list. `false`, the\ndefault, does not filter.\n","schema":{"type":"boolean","default":false}},{"name":"customer_id","in":"query","description":"Filter by customer UUID","schema":{"$ref":"#/components/schemas/UUID"}},{"name":"date_from","in":"query","description":"Date from, inclusive (YYYY-MM-DD). It applies to the operation date when set, and to the\nissue date otherwise.\n","schema":{"type":"string","format":"date"}},{"name":"date_to","in":"query","description":"Date to, inclusive (YYYY-MM-DD). Applies to the same date as `date_from`.","schema":{"type":"string","format":"date"}},{"name":"invoice_number","in":"query","description":"Search by invoice number (e.g., 2025/0001)","schema":{"type":"string"}},{"name":"recipient_name","in":"query","description":"Filter by recipient's fiscal name (partial, case-insensitive search)","schema":{"type":"string"}},{"name":"recipient_nif","in":"query","description":"Filter by recipient's NIF (partial search)","schema":{"type":"string"}},{"name":"series_code","in":"query","description":"Filter by series code (exact match, case-insensitive). Use `search` for partial matching across the invoice number, recipient and series code.","schema":{"type":"string"}},{"name":"external_ref","in":"query","description":"Filter by exact external reference (client-supplied order/cart/contract id).","schema":{"type":"string"}},{"name":"rectified_invoice_id","in":"query","description":"Return the corrective invoices that correct this invoice. Accepts the id of an\nissued invoice; a single invoice can have several partial correctives.\n","schema":{"type":"string","format":"uuid"}},{"name":"taxable_base_min","in":"query","description":"Minimum taxable base","schema":{"type":"number","format":"double"}},{"name":"taxable_base_max","in":"query","description":"Maximum taxable base","schema":{"type":"number","format":"double"}},{"name":"total_min","in":"query","description":"Minimum invoice total","schema":{"type":"number","format":"double"}},{"name":"total_max","in":"query","description":"Maximum invoice total","schema":{"type":"number","format":"double"}},{"name":"verifactu_status","in":"query","description":"Filter by the VeriFactu submission status of the invoice, using the very same\nvocabulary that `verifactu.submission_status` publishes on each invoice.\n`NOT_SUBMITTED` selects issued invoices with VeriFactu enabled whose\nregistration never happened (no live record).\n","schema":{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"}},{"name":"verifactu_enabled","in":"query","description":"Filter by whether VeriFactu is enabled for the invoice — the same flag published as\n`verifactu.enabled`. `false` returns the invoices that never reach AEAT.\n","schema":{"type":"boolean"}},{"name":"metadata","in":"query","description":"Filter by metadata key/value pairs (exact match, AND between keys).\nRepeat the bracket-style param to filter on multiple keys.\nMax 50 pairs per request. Keys must match `^[A-Za-z0-9_\\-.]{1,64}$`.\nExample: `?metadata[external_order_id]=ORD-42&metadata[tenant]=acme`\n","required":false,"style":"deepObject","explode":true,"schema":{"type":"object","additionalProperties":{"type":"string"},"maxProperties":50}},{"name":"sort_by","in":"query","description":"Field to sort by: `issue_date` (default), `operation_date`, `due_date`,\n`invoice_number`, `series_code`, `status`, `invoice_total`, `taxable_base`,\n`total_vat`, `total_equivalence_surcharge`, `total_discounts`, `recipient_name`,\n`recipient_nif`, `created_at` or `updated_at`. Any other value is rejected with `400`\n`VALIDATION_ERROR`, whose `details` name `sort_by` and the accepted values.\n`invoice_number` sorts by series code and then by the number within the series,\ncompared numerically (`F-9` before `F-10`). Ties on any field are broken by series\ncode, then by the number within the series and then by id, in the requested direction.\n","schema":{"type":"string"}},{"name":"sort_order","in":"query","description":"Sort direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"desc"}}],"responses":{"200":{"description":"Invoice list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["invoices","pagination"],"properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"examples":{"list_success":{"$ref":"#/components/examples/list-invoices-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"A parameter parsed as its declared type but its value is not one the operation admits:\na `limit` outside its range (`limit=0`, `limit=101`), a `page` under 1, a `cursor` that is\nnot one this API issued, more keys than the `metadata` filter accepts, a value outside the\nsupported set. The query was rejected whole and nothing was read.\n\nWhere a `400` says the target could not be understood, this says it was understood and\nrefused. Fix the value — retrying the same query answers the same way.\n\nOther `error.code` values with this status:\n\n- `METADATA_FILTER_TOO_MANY_KEYS` — the query sends more than 50 distinct `metadata[key]` filters; narrow the filter to 50 keys or fewer.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Validation constraint violation.","details":{"limit":"must be greater than or equal to 1"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"createCompanyInvoice","tags":["CompanyInvoices"],"summary":"Create an invoice","description":"Creates an invoice for this company. The issuer data comes from the company in the path,\nand the document is created as a draft unless you ask for it to be issued.\n\n- **Issuing:** `options.issue_directly` numbers and issues the invoice in the same call.\n  Submission to the AEAT is asynchronous, so `verifactu.submission_status` comes back as\n  `PENDING`: a 2xx means the invoice was accepted for submission, not that the AEAT has\n  registered it.\n  If the number the series would assign is already used by another invoice of the same\n  company, in this series or in another one, it fails with `400 SERIES_NUMBER_COLLISION`\n  without issuing anything or consuming a number: the series needs review, so contact\n  support.\n- **Document type:** `type` chooses the document. A `PROFORMA` is non-fiscal — it is born\n  `ACTIVE`, numbered `PRO-...` from its own non-fiscal series, and ignores\n  `issue_directly`.\n- **Related:** to copy an existing invoice into a new draft, use\n  `POST …/invoices/derivations`, which carries neither `type`, nor `recipient`, nor\n  `lines`.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"wait_for_pdf","in":"query","required":false,"description":"Same flag as `options.wait_for_pdf`. Only applies when the invoice is issued in this\ncall (`options.issue_directly: true`).\n","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceRequest"},"examples":{"basic_invoice":{"$ref":"#/components/examples/create-basic"},"adhoc_invoice":{"$ref":"#/components/examples/create-with-adhoc-customer"},"issue_directly":{"$ref":"#/components/examples/create-issue-directly"},"custom_email":{"$ref":"#/components/examples/create-with-custom-email"},"simplified_under_3000":{"$ref":"#/components/examples/under-3000"},"standard_spanish_b2b":{"$ref":"#/components/examples/spanish-b2b"},"standard_multiple_vat":{"$ref":"#/components/examples/multiple-vat-rates"},"line_by_declared_total_out_of_range":{"$ref":"#/components/examples/derived_unit_price_out_of_range_request"},"standard_irpf":{"$ref":"#/components/examples/irpf-withholding"},"standard_recargo":{"$ref":"#/components/examples/recargo-equivalencia"},"standard_eu_goods_e5":{"$ref":"#/components/examples/b2b-intra-eu-goods-e5"},"standard_eu_services_n2":{"$ref":"#/components/examples/b2b-intra-eu-services-n2"},"standard_b2c_oss_over":{"$ref":"#/components/examples/b2c-oss-over-threshold"},"standard_b2c_oss_under":{"$ref":"#/components/examples/b2c-under-oss-threshold"},"standard_export_e2":{"$ref":"#/components/examples/export-non-eu-goods-e2"},"standard_services_noeu":{"$ref":"#/components/examples/services-non-eu-n2"},"standard_exempt_art20":{"$ref":"#/components/examples/exempt-art-20-medical"},"standard_igic":{"$ref":"#/components/examples/igic-canary-islands"},"standard_not_subject":{"$ref":"#/components/examples/not-subject-art-7-9"},"standard_isp_reverse":{"$ref":"#/components/examples/isp-reverse-charge-s2"},"standard_rebu":{"$ref":"#/components/examples/rebu-used-goods"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"created_success":{"$ref":"#/components/examples/created-success"}}}}},"400":{"description":"One of:\n- `INVALID_JSON_FORMAT`: the body is not valid JSON, or a property has the wrong type or\n  format. `details` follows `FieldDeserializationError`: `field`, `invalid_value` and, where\n  there is one, `expected_format`.\n- `SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`: a `SIMPLIFIED` invoice whose total, VAT\n  included, is above 3,000€. The same answer when the invoice is created and when an edit\n  takes it over the cap, whether the edit changes the lines or changes `type` to\n  `SIMPLIFIED`. Issue a `STANDARD` invoice with the customer identified instead.\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `INVOICE_NO_LINES` — the invoice was sent with an empty `lines` array; send at least one line.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_FOUND` — an id in `source_invoice_ids` does not exist, is in another environment, or is not visible to this account or its managed accounts.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_ISSUED` — a source invoice in `source_invoice_ids` is still a draft (or not in an issued state); issue it first.\n- `LINE_SUPLIDO_TOO_MANY_SOURCES` — more than 200 distinct `source_invoice_ids` in one request; split the suplido across invoices.\n- `LINE_SUPLIDO_TOO_MANY_SOURCE_ACCOUNTS` — the `source_invoice_ids` belong to more than 50 managed accounts; split the request.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECIPIENT_NIF_AND_ID_OTHER_EXCLUSIVE` — the inline recipient carries both `nif` and `alternative_id`; send only one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalid_json_format":{"summary":"A property with the wrong format","value":{"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"}}},"error_simplified_exceeds_limit":{"$ref":"#/components/examples/exceeds_3000"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A resource the body references does not exist, or is not one this credential can see:\n`INVOICE_SERIES_NOT_FOUND` for `series_id`, `INVOICE_CLIENT_NOT_FOUND` for\n`recipient.customer_id`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict. One of:\n- Idempotency key already processed, or duplicate invoice number.\n- `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`: a live invoice with the same\n  `external_ref` already exists.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"conflict":{"$ref":"#/components/examples/conflict-409"}}}}},"422":{"description":"Validation error, or (when creating and issuing directly) the company/NIF is not ready\nto issue in this environment. In the latter case `error.code` is `EMISSION_NOT_READY`\nand `error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`, `NIF_NOT_REGISTERED`,\n`NIF_REPRESENTATION_REQUIRED`). When a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAt issue time the generated number must also fit the AEAT invoice number: more than 60\ncharacters answers `INVOICE_NUMBER_TOO_LONG`, a character outside printable ASCII or one\nof `\"`, `'`, `<`, `>`, `=` answers `INVOICE_NUMBER_INVALID_CHARACTERS`. In both cases the\ninvoice is not issued and the number is not used; issue it with another series, or fix the\nseries code or format while the series has no issued invoices, and retry.\nA SIMPLIFIED invoice whose recipient carries a NIF or an alternative identifier is\nrejected with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`, at any amount: issue a\nSTANDARD invoice instead.\nWith VeriFactu, a `NOT_REGISTERED` (07) recipient whose DNI or NIE the census identifies\nanswers `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` before numbering (see `AlternativeIdentifier`);\ntype 07 is never used for a company, whose name is not verified.\nWhen the invoice is recorded with VeriFactu, its tax breakdown must also fit one AEAT\nrecord: lines are grouped by tax, regime key, operation type, exemption, tax rate and\nequivalence surcharge rate, and more than 12 different groups answers\n`INVOICE_TAX_BREAKDOWN_TOO_LONG`, and a tax or surcharge rate with more than two decimals\nanswers `INVOICE_TAX_RATE_TOO_MANY_DECIMALS`. AEAT requires the Macrodato flag on a record of\n100,000,000.00 EUR or more in absolute value; such an invoice, corrective invoices included,\nanswers `INVOICE_TOTAL_TOO_LARGE_FOR_VERIFACTU` and is not numbered.\nThe recipient's `legal_name` is recorded as it is on the invoice,\nso with VeriFactu it must be at most 120 characters: a longer one answers\n`FIELD_TOO_LONG`. BeeL. builds the record it will send before numbering and checks it against AEAT's validations: an\ninvoice with nothing but disbursements (`SUPLIDO` lines) is not an invoice and answers\n`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`, and any other record AEAT would reject answers the specific\ncode or `VERIFACTU_RECORD_NOT_DECLARABLE`, with the AEAT section in its message. In all\nthese cases the invoice is not issued and the number is not used.\nFiscal rules on the lines answer `422` too, each with its own `error.code`. A\n`SIMPLIFIED` invoice (or its R5 corrective) cannot carry reverse charge\n(`SIMPLIFICADA_FORBIDS_ISP`), an equivalence surcharge (`SIMPLIFICADA_FORBIDS_SURCHARGE`)\nor an operation of art. 4.4 RD 1619/2012 such as an intra-EU supply\n(`SIMPLIFICADA_FORBIDS_CROSS_BORDER`). An `exemption_reason` that needs the recipient to\ncarry a specific identifier type answers `EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` (today,\n`EXENTA_ART_25` needs `NIF_IVA`); one no longer accepted on a new invoice answers\n`EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a `regime_key` that requires an exemption the line does\nnot carry answers `REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION`. A VAT `percentage` that is not\none of the accepted rates answers `INVALID_PERCENTAGE`, and one AEAT does not accept on the\noperation date (the issue date when there is none) answers `VAT_RATE_NOT_ACCEPTED_ON_DATE`,\nwhose message names the dates that rate is accepted. A number outside what its field\nstores answers `VALUE_OUT_OF_RANGE`.\n\nOther `error.code` values with this status:\n\n- `CORRECTIVE_INVOICE_INCOMPLETE` — `type: CORRECTIVE` is not accepted when creating an invoice; create correctives from the original with `POST …/invoices/{invoice_id}/corrective`.\n- `DUE_DATE_BEFORE_ISSUE_DATE` — `due_date` is earlier than the issue date (today on create, the stored one on edit); send a due date on or after it.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `LINE_SUPLIDO_MUST_HAVE_NO_IRPF` — a `SUPLIDO` line is outside VAT and cannot carry `irpf_rate`; omit it or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_RECARGO` — a `SUPLIDO` line cannot carry an equivalence surcharge; omit `equivalence_surcharge_rate` or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_TAX` — a `SUPLIDO` line (payment on the client's behalf, art. 78.Tres.3 LIVA) cannot carry a `main_tax` with a non-zero rate; remove it.\n- `LINE_SUPLIDO_REFERENCE_REQUIRED` — a `SUPLIDO` line needs `source_invoice_reference` (or `source_invoice_ids`, from which it is built); say which third-party invoice it passes on.\n- `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.\n- `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`.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PROFORMA_RECIPIENT_REQUIRED` — a proforma needs a recipient: send `customer_id` or the recipient's data.\n- `RECIPIENT_ADDRESS_REQUIRED` — an inline recipient (no `customer_id`) on a non-simplified invoice needs an `address` with at least `street`.\n- `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE` — `customer_id` was sent together with inline recipient fields; send one or the other.\n- `RECIPIENT_FISCAL_NAME_REQUIRED` — an inline recipient (no `customer_id`) on a non-simplified invoice needs a non-blank `legal_name`.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECIPIENT_NOT_PROVIDED` — a standard invoice was sent with an empty `recipient`; send `customer_id` or the recipient's data.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"},"error_invoice_empty_lines":{"$ref":"#/components/examples/response_example-2"},"error_invoice_invalid_nif":{"$ref":"#/components/examples/response_example-3"},"error_invoice_required_fields":{"$ref":"#/components/examples/response_example-4"},"error_simplified_identified_recipient":{"$ref":"#/components/examples/response_example"},"error_derived_unit_price_out_of_range":{"$ref":"#/components/examples/derived_unit_price_out_of_range_response"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/derivations":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyInvoiceDerivation","tags":["CompanyInvoices"],"summary":"Derive a draft invoice from an existing one","description":"Creates a draft invoice derived from an existing invoice of this company. The source\ninvoice, named in `from_invoice_id`, is not modified.\n\n- **`mode`:** the only value is `DUPLICATE`, which copies the source into a fresh draft. Recipient, lines,\n  payment method, series and observations are copied; number, status, dates, VeriFactu\n  data and PDF are reset.\n- **Series:** the one sent in `series_id`, or the source's when omitted. It is validated\n  against the type of the copy, which is not always the source's: the copy of a\n  `CORRECTIVE` is born `STANDARD`. An incompatible series fails with\n  `422 SERIES_INCOMPATIBLE_DOC_TYPE`.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceDerivationRequest"},"examples":{"simple_duplicate":{"summary":"Simple duplication (defaults)","value":{"from_invoice_id":"550e8400-e29b-41d4-a716-446655440001","mode":"DUPLICATE"}},"duplicate_other_series":{"summary":"Duplication to different series","value":{"from_invoice_id":"550e8400-e29b-41d4-a716-446655440001","mode":"DUPLICATE","series_id":"550e8400-e29b-41d4-a716-446655440099"}}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Draft invoice derived successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The `from_invoice_id` does not exist under this company. An invoice belonging to\nanother NIF answers the same way.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `LINE_NEGATIVE_PRICE` — a non-corrective document cannot have a negative unit price (e.g. duplicating a corrective); use a negative quantity or a corrective invoice.\n- `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`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/batches":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyInvoiceBatch","tags":["CompanyInvoiceDelivery"],"summary":"Apply one operation to a set of invoices","description":"Applies one operation to a set of invoices of this company and reports, invoice by\ninvoice, which succeeded and which failed.\n\n- **Operations:** `ISSUE` issues the draft invoices; `STATUS` moves them to the\n  `new_status` given in the body.\n- **Limit:** up to 50 invoices per request (`invoice_ids`).\n- **Not atomic:** each invoice is processed on its own, and since issuing is\n  irreversible, the ones already issued stay issued if a later one fails.\n- **Related:** downloading PDFs, sending email and exporting are not operations of this\n  batch — use `…/invoices/pdf-archive`, `…/invoices/deliveries` and `…/invoices/exports`.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceBatchRequest"},"examples":{"issue_drafts":{"summary":"Issue reviewed drafts","value":{"operation":"ISSUE","invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"]}},"mark_as_sent":{"summary":"Mark invoices as sent","value":{"operation":"STATUS","invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"new_status":"SENT"}},"mark_as_paid":{"summary":"Mark invoices as paid","value":{"operation":"STATUS","invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"new_status":"PAID","payment_date":"2025-01-15"}}}}}},"responses":{"200":{"description":"Batch completed (may include partial failures)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkOperationResponse"}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `PAYMENT_DATE_REQUIRED` — `new_status: PAID` was sent without `payment_date`; add the date the invoices were paid.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/pdf-archive":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyInvoicePdfArchive","tags":["CompanyInvoiceDelivery"],"summary":"Download the PDFs of several invoices as a ZIP","description":"Returns a single ZIP with the PDFs of the invoices of this company named in\n`invoice_ids`.\n\n- **Limit:** up to 500 invoices per request.\n- **Missing PDFs:** invoices whose PDF is not available are left out of the archive. If\n  no PDF at all is available the call fails with `400`.\n- **Counts:** `X-Bulk-Total`, `X-Bulk-Successful` and `X-Bulk-Failed` response headers\n  report how many PDFs were requested and how many made it into the ZIP.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoicePdfArchiveRequest"},"examples":{"download_5_invoices":{"summary":"Download 5 invoices","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002","550e8400-e29b-41d4-a716-446655440003","550e8400-e29b-41d4-a716-446655440004","550e8400-e29b-41d4-a716-446655440005"]}}}}}},"responses":{"200":{"description":"ZIP file with invoice PDFs","headers":{"X-Bulk-Total":{"description":"Total number of requested invoices","schema":{"type":"integer"}},"X-Bulk-Successful":{"description":"Number of PDFs included in ZIP","schema":{"type":"integer"}},"X-Bulk-Failed":{"description":"Number of PDFs that could not be included","schema":{"type":"integer"}}},"content":{"application/zip":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"No valid PDFs available or invalid request\n\nOther `error.code` values with this status:\n\n- `NO_PDFS_AVAILABLE` — none of the requested invoices produced a PDF (not found, or issued but PDF not generated yet); no archive is returned. Retry once the PDFs exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/deliveries":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyInvoiceDelivery","tags":["CompanyInvoiceDelivery"],"summary":"Send several invoices in one email","description":"Sends one email carrying the PDFs of several invoices of this company as attachments.\n\n- **`recipients`:** required, and must carry at least one address; no\n  address is inferred from any profile.\n- **Limit:** up to 200 invoices per message (`invoice_ids`).\n- **Failures:** invoices whose PDF cannot be attached are reported in `failures`, and the\n  message is still sent with the rest.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceDeliveryRequest"},"examples":{"send_to_accountant":{"summary":"Send to accountant","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002"],"recipients":["accountant@example.com"],"subject":"Invoices for January 2025","message":"Please find attached the invoices from last month for your review."}},"send_with_cc":{"summary":"Send with CC","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001"],"recipients":["accountant@example.com"],"cc":["copy@example.com"]}}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["email_id","sent_to","sent_at","total_invoices","invoices_attached"],"properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time"},"total_invoices":{"type":"integer","description":"Total number of requested invoices"},"invoices_attached":{"type":"integer","description":"Number of PDFs successfully attached"},"failures":{"type":"array","description":"List of invoices that could not be attached","items":{"type":"object","properties":{"invoice_id":{"$ref":"#/components/schemas/UUID"},"error_code":{"type":"string","enum":["NOT_FOUND","PDF_NOT_GENERATED","INVALID_STATE","INTERNAL_ERROR"]},"error_message":{"type":"string"}}}}}}}}]}}}},"400":{"description":"No valid invoices to send or no recipients specified\n\nOther `error.code` values with this status:\n\n- `BULK_EMAIL_NO_VALID_INVOICES` — none of the requested invoices could be attached (not found, draft or scheduled, or PDF not generated yet); nothing was sent. Fix them and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/exports":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyInvoiceExport","tags":["CompanyInvoiceDelivery"],"summary":"Export invoices","description":"Produces a spreadsheet with the invoices of this company and returns the file in the\nresponse.\n\n- **Selection:** the invoices named in `invoice_ids`, or, when that is absent, the ones\n  matching `filters`. A request with neither is rejected with\n  `400 EXPORT_SELECTION_REQUIRED`.\n- **Format:** `SUMMARY` writes one row per invoice with aggregated totals, `ITEMS` one\n  row per invoice line. To get both, send `formats: [SUMMARY, ITEMS]`: a single file\n  with one sheet per format, rather than two requests.\n- **Disbursements sheet:** when any exported invoice has `SUPLIDO` lines, the workbook\n  gets one more sheet at the end, named `Suplidos`, with one row per source invoice.\n  Columns: «Número factura», «Fecha expedición», «Cliente», «NIF cliente», «Factura\n  origen», «NIF emisor origen» and «Importe factura origen». The amount is the\n  `total_to_pay` of the source invoice; for a manual disbursement with no linked\n  invoices it is the line total. When the source invoice is not visible to you, the row\n  carries only its number, when known, and the issuer NIF and the amount are empty. The\n  sheet is added whatever `formats` you send, and it is not a format: you cannot\n  request it or opt out of it.\n- **Values:** the status, payment method, tax type and line type columns carry the same\n  values the API returns (`ISSUED`, `BANK_TRANSFER`, `IVA`, `SUPLIDO`…). A proforma past\n  its validity is exported as `ACTIVE`, not `EXPIRED`.\n- **Limit:** up to 50000 invoices per export. Going over it is rejected with\n  `422 EXPORT_LIMIT_EXCEEDED` — narrow the date range or split the selection. The\n  export is never silently truncated.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceExportRequest"},"examples":{"export_by_ids":{"summary":"Export specific invoices (default format)","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001","550e8400-e29b-41d4-a716-446655440002","550e8400-e29b-41d4-a716-446655440003"]}},"export_items_format":{"summary":"Export as items (one row per line)","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001"],"format":"ITEMS"}},"export_summary_and_items":{"summary":"Summary and items in one file (one sheet each)","value":{"invoice_ids":["550e8400-e29b-41d4-a716-446655440001"],"formats":["SUMMARY","ITEMS"]}},"export_q1_2025":{"summary":"Export Q1 2025 invoices","value":{"filters":{"date_from":"2025-01-01","date_to":"2025-03-31"}}},"export_paid":{"summary":"Export all paid invoices","value":{"filters":{"status":"PAID"}}}}}}},"responses":{"200":{"description":"Export generated successfully","headers":{"Content-Disposition":{"description":"Suggested filename for download","schema":{"type":"string"},"example":"attachment; filename=\"facturas_2025-01-15.xlsx\""},"X-Total-Invoices":{"description":"Number of invoices included in the export","schema":{"type":"integer"}}},"content":{"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"`EXPORT_SELECTION_REQUIRED` — the request names no invoices: neither a non-empty `invoice_ids` nor a single filter. An export with no criteria is rejected, never answered with the whole account.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanyInvoice","tags":["CompanyInvoices"],"summary":"Get an invoice","description":"Retrieves the full details of an invoice of this company.","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Invoice retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"get_success":{"$ref":"#/components/examples/get-invoice-success"},"stripe_generated":{"$ref":"#/components/examples/generated-invoice"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyInvoice","tags":["CompanyInvoices"],"summary":"Update an invoice","description":"Updates only the fields present in the body, leaving every other field of the invoice as\nit is.\n\n- **Status:** only an invoice in `DRAFT` or `SCHEDULED`, or a proforma in `ACTIVE`, can be\n  modified; any other status answers `422` with `STATUS_NOT_MODIFIABLE`. An issued one is\n  amended with a corrective invoice (`POST …/{invoice_id}/corrective`) or voided.\n- **Series:** changing `series_id` never moves the invoice to another NIF — a series of\n  another company is not visible from here.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInvoiceRequest"},"examples":{"update_invoice":{"$ref":"#/components/examples/update-invoice"}}}}},"responses":{"200":{"description":"Invoice updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"One of:\n- `INVALID_JSON_FORMAT`: the body is not valid JSON, or a property has the wrong type or\n  format. `details` follows `FieldDeserializationError`: `field`, `invalid_value` and, where\n  there is one, `expected_format`.\n- `SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`: a `SIMPLIFIED` invoice whose total, VAT\n  included, is above 3,000€. The same answer when the invoice is created and when an edit\n  takes it over the cap, whether the edit changes the lines or changes `type` to\n  `SIMPLIFIED`. Issue a `STANDARD` invoice with the customer identified instead.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_FOUND` — an id in `source_invoice_ids` does not exist, is in another environment, or is not visible to this account or its managed accounts.\n- `LINE_SUPLIDO_SOURCE_INVOICE_NOT_ISSUED` — a source invoice in `source_invoice_ids` is still a draft (or not in an issued state); issue it first.\n- `LINE_SUPLIDO_TOO_MANY_SOURCES` — more than 200 distinct `source_invoice_ids` in one request; split the suplido across invoices.\n- `LINE_SUPLIDO_TOO_MANY_SOURCE_ACCOUNTS` — the `source_invoice_ids` belong to more than 50 managed accounts; split the request.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECIPIENT_REQUIRED_FOR_ORDINARY` — the edit sent an empty `recipient` on a non-simplified invoice; its recipient can be replaced but not removed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalid_json_format":{"summary":"A property with the wrong format","value":{"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"}}},"error_simplified_exceeds_limit":{"$ref":"#/components/examples/exceeds_3000"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nA SIMPLIFIED invoice whose recipient carries a NIF or an alternative identifier is\nrejected with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`, at any amount: issue a\nSTANDARD invoice instead.\nFiscal rules on the lines answer `422` too, each with its own `error.code`. A\n`SIMPLIFIED` invoice (or its R5 corrective) cannot carry reverse charge\n(`SIMPLIFICADA_FORBIDS_ISP`), an equivalence surcharge (`SIMPLIFICADA_FORBIDS_SURCHARGE`)\nor an operation of art. 4.4 RD 1619/2012 such as an intra-EU supply\n(`SIMPLIFICADA_FORBIDS_CROSS_BORDER`). An `exemption_reason` that needs the recipient to\ncarry a specific identifier type answers `EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` (today,\n`EXENTA_ART_25` needs `NIF_IVA`); one no longer accepted on a new invoice answers\n`EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a `regime_key` that requires an exemption the line does\nnot carry answers `REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION`. A VAT `percentage` that is not\none of the accepted rates answers `INVALID_PERCENTAGE`, and one AEAT does not accept on the\noperation date (the issue date when there is none) answers `VAT_RATE_NOT_ACCEPTED_ON_DATE`,\nwhose message names the dates that rate is accepted. A number outside what its field\nstores answers `VALUE_OUT_OF_RANGE`.\n\nOther `error.code` values with this status:\n\n- `DUE_DATE_BEFORE_ISSUE_DATE` — `due_date` is earlier than the issue date (today on create, the stored one on edit); send a due date on or after it.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `LINE_SUPLIDO_MUST_HAVE_NO_IRPF` — a `SUPLIDO` line is outside VAT and cannot carry `irpf_rate`; omit it or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_RECARGO` — a `SUPLIDO` line cannot carry an equivalence surcharge; omit `equivalence_surcharge_rate` or send 0.\n- `LINE_SUPLIDO_MUST_HAVE_NO_TAX` — a `SUPLIDO` line (payment on the client's behalf, art. 78.Tres.3 LIVA) cannot carry a `main_tax` with a non-zero rate; remove it.\n- `LINE_SUPLIDO_REFERENCE_REQUIRED` — a `SUPLIDO` line needs `source_invoice_reference` (or `source_invoice_ids`, from which it is built); say which third-party invoice it passes on.\n- `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.\n- `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`.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `PROFORMA_TYPE_CHANGE_FORBIDDEN` — an edit cannot turn a proforma into an invoice or an invoice into a proforma; use `convert-to-invoice` or create a new document.\n- `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE` — `customer_id` was sent together with inline recipient fields; send one or the other.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `STATUS_NOT_MODIFIABLE` — the invoice's current status does not allow this change: only drafts, scheduled invoices and active proformas can be edited, and only issued, sent or overdue invoices can be marked paid.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `TYPE_CHANGE_CORRECTIVE_FORBIDDEN` — a draft cannot change `type` to or from `CORRECTIVE`; correctives are created from the original with `POST …/corrective`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"},"error_simplified_identified_recipient":{"$ref":"#/components/examples/response_example"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyInvoice","tags":["CompanyInvoices"],"summary":"Delete a draft invoice","description":"Deletes a draft invoice of this company. The record is marked as deleted rather than\nremoved.\n\n- **Issued invoices:** never deleted. They are voided with `POST …/{invoice_id}/void`,\n  which leaves the fiscal trail.\n- **`source_proforma_id`:** when the draft came from converting a proforma, deleting it\n  returns that proforma from `CONVERTED` to `ACTIVE`, editable and convertible again.\n  Voiding or rectifying an issued invoice does not return its proforma; only deleting the\n  draft does.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"Invoice deleted successfully"},"400":{"description":"`STATUS_NOT_DELETABLE`: the invoice cannot be deleted in its current status.\n\nOther `error.code` values with this status:\n\n- `DELETION_FAILED` — the draft was deleted by another request between the read and the delete. Fetch it again: it is most likely already gone.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/verifactu-records":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"listCompanyInvoiceVerifactuRecords","tags":["CompanyInvoices"],"summary":"List the VeriFactu records of an invoice","description":"Returns the VeriFactu records of this invoice, each with its own status, ordered by\n`registered_at` ascending: the registration first and, if the invoice was voided, its\ncancellation after it. A record rejected before reaching the AEAT is listed too, as\n`REJECTED`, until the invoice is submitted again: the new record then replaces it.\n\n- **No records:** an invoice that was never submitted (a draft, or one outside VeriFactu)\n  answers `200` with an empty list.\n\n**Closed catalogue.** This collection is fixed and bounded: it carries no `pagination`, it\ntakes no `page`/`limit`, and every response holds the whole set.\n","x-closed-catalogue":true,"x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"VeriFactu records retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["records"],"properties":{"records":{"type":"array","items":{"$ref":"#/components/schemas/VeriFactuRecord"}}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/pdf":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanyInvoicePdf","tags":["CompanyInvoiceDelivery"],"summary":"Get the PDF download URL of an invoice","description":"Returns a temporary pre-signed URL to download the invoice PDF.\n\n- **URL:** expires in five minutes and only allows `GET`.\n- **Waiting:** a PDF is produced asynchronously, so this request **waits** for it (up to\n  ten seconds) instead of handing you a polling loop to write. Bound the wait with\n  `Prefer: wait=N`, or opt out with `Prefer: wait=0`.\n- **`202`:** only when the wait elapsed with the PDF still in flight. No body is returned;\n  ask again after `Retry-After`.\n- **Drafts:** a draft has no fiscal PDF and answers `400 INVOICE_NOT_ISSUED_NO_PDF`\n  immediately — that one never waits. Issue it, or render it with\n  `GET …/{invoice_id}/pdf/preview`.\n- **Not registered with the AEAT:** under VeriFactu the PDF carries the QR code of the\n  invoice's registration. An invoice whose registration was rejected before reaching the\n  AEAT, or that was voided without ever being registered, has no PDF and answers\n  `400 INVOICE_NOT_REGISTERED_NO_PDF` immediately. Its `verifactu.error_message` says why.\n- **Never modified:** the PDF of an issued invoice is generated once — with its VeriFactu QR\n  when it applies — and stays the document you delivered. Voiding the invoice or issuing\n  a corrective against it does not change the PDF: read the invoice's `status` to know it.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"Prefer","in":"header","required":false,"description":"RFC 7240 preference bounding how long this request may wait for a PDF that is still\nbeing generated: `Prefer: wait=N`, with `N` in seconds.\n\nBy default the request waits (up to the server cap) and answers `200` with the URL, so\nthe `202` is the exception rather than the normal path. Use `Prefer: wait=0` to opt out\nand get the old poll-only behaviour: an immediate `202` while the PDF is in flight.\n\nA value above the cap is lowered to it, and the `202` then echoes what was actually\napplied in `Preference-Applied: wait=<seconds>` — so you never have to discover the cap\nby trial and error. A value that is not a non-negative integer is ignored altogether\n(RFC 7240: a preference that is not understood is not an error), and the request falls\nback to the default wait with no `Preference-Applied` header.\n","schema":{"type":"string"},"example":"wait=5"}],"responses":{"200":{"description":"Download URL generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicePdfResponse"}}}},"202":{"description":"The PDF was still not ready when the wait window elapsed, so no body is returned.\nUncommon: the request waits for the PDF by default and normally answers `200`. It is\nwhat you always get with `Prefer: wait=0`. Ask again — `Retry-After` says how soon.\n","headers":{"Retry-After":{"description":"Seconds to wait before asking again.","schema":{"type":"integer"}},"Preference-Applied":{"description":"Echoes the wait actually applied (`wait=<seconds>`) when the request sent a\n`Prefer: wait=N` that was honoured — lower than asked if it exceeded the cap.\n","schema":{"type":"string"}}}},"400":{"description":"`INVOICE_NOT_ISSUED_NO_PDF` — the invoice is a draft: it has no fiscal PDF and no emission is generating one. Issue it, or use `…/pdf/preview`. `INVOICE_NOT_REGISTERED_NO_PDF` — the invoice is not registered with the AEAT, so there is no QR code to print and no PDF: its registration was rejected before reaching the AEAT, or it was voided without being registered. Answered immediately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/preview":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanyInvoicePreview","tags":["CompanyInvoiceDelivery"],"summary":"Get the preview image URL of an invoice","description":"Returns a temporary pre-signed URL to a preview image (WebP) of the invoice, suitable for\ninline rendering. The image is generated and cached on first request, so a later call\nreturns the cached image. The URL expires in five minutes and only allows `GET`.\n\nThe image of an issued invoice is taken from its stored PDF, the one delivered. While that\nPDF does not exist yet — right after issuing (whether or not the invoice goes through\nVeriFactu, since the PDF is generated asynchronously) or, for a VeriFactu invoice\nspecifically, while it waits for the QR of its registration — the answer is `202`, as for\nthe PDF download.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Preview image URL generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoicePreviewResponse"}}}},"202":{"description":"The invoice's PDF, which the image is taken from, is not generated yet: no body. Ask\nagain — `Retry-After` says how soon.\n","headers":{"Retry-After":{"description":"Seconds to wait before asking again.","schema":{"type":"integer"}}}},"400":{"description":"`INVOICE_NOT_REGISTERED_NO_PDF`: the invoice has no VeriFactu registration, so it has no\nPDF and no preview image. The reason is in `verifactu.error_message`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/pdf/preview":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"previewCompanyInvoicePdf","tags":["CompanyInvoiceDelivery"],"summary":"Preview the PDF of a draft invoice","description":"Renders the PDF of a draft invoice on the fly, without storing it and without consuming\nnumbering, so each call reflects the latest changes. The response is `application/pdf`.\n\n- **Document:** shows `BORRADOR` in place of the invoice number and carries no VeriFactu\n  QR code. The issuer block is taken from the company in the path.\n- **Drafts only:** an invoice that is not a draft answers `400` — it has a stored PDF,\n  available at `GET …/{invoice_id}/pdf`.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"PDF preview generated successfully","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"The invoice is not a draft, so it has a stored PDF instead of a preview\n\nOther `error.code` values with this status:\n\n- `PREVIEW_DRAFT_ONLY` — the invoice is already issued (or voided); preview only works on drafts, scheduled invoices and proformas. Download its real PDF instead.\n- `PREVIEW_GENERATION_ERROR` — the preview PDF could not be rendered. Nothing changed on the invoice; retry later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/send":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"post":{"operationId":"sendCompanyInvoice","tags":["CompanyInvoiceDelivery"],"summary":"Send an invoice by email","description":"Sends the invoice by email, attaching its PDF by default. When no recipient is given, the\naddresses configured on the customer are used.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendEmailRequest"},"examples":{"send_invoice_email":{"$ref":"#/components/examples/simple_send"},"send_invoice_email_multiple":{"$ref":"#/components/examples/multiple_recipients"}}}}},"responses":{"200":{"description":"Email sent successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time"}}}}}]}}}},"202":{"description":"Accepted, not sent yet: `attach_pdf` is `true` and the invoice's PDF is not generated\nyet. This can happen right after issuing, whether or not the invoice goes through\nVeriFactu — the PDF is generated asynchronously — or, for a VeriFactu invoice\nspecifically, while it waits for the QR of its registration. The email goes out\nshortly after the PDF is stored, with it attached (it can take several minutes if\nVeriFactu or PDF generation is delayed). Follow it with `email_id`. If the invoice ends\nup with no PDF (a VeriFactu registration that ends without a QR, or PDF generation that\nfails for good), or its PDF is not stored within a day, the email is marked as failed,\nwith its reason.\n\n`sent_at` here is when the request was ACCEPTED, not when the email actually went out\n— it has not, yet. The `invoice.email.sent` webhook tells you when it goes out; a\nfailed email has no webhook, so check its status by `email_id`.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"email_id":{"$ref":"#/components/schemas/UUID"},"sent_to":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"sent_at":{"type":"string","format":"date-time","description":"The moment this request was accepted, not the moment the email was\nsent — it has not been sent yet. See the `202` description.\n"}}}}}]}}}},"400":{"description":"The invoice cannot be sent: `INVOICE_DRAFT_NOT_SENDABLE` when it is not issued yet, a\ndraft or a scheduled invoice (issue it first), `INVOICE_NOT_REGISTERED_NO_PDF` when `attach_pdf` is `true` and the invoice has\nno VeriFactu registration, so it has no PDF (the reason is in\n`verifactu.error_message`).\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICES_REQUIRES_PDF` — `attach_source_invoices: true` was sent with `attach_pdf: false`; the source-invoice ZIP only travels alongside the invoice PDF. Set `attach_pdf: true`.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The company does not belong to the authenticated account, or the message is not allowed\nto reach one of its recipients (`ENVIO_NO_PERMITIDO`). Test environments only deliver to\nthe account holder's own address; every address in `recipients` and in `cc` is checked.\nA company that is not yours to reach answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"`INVOICE_EMAIL_NO_RECIPIENTS`: the request sends no recipients and the recipient of the\ninvoice has no email address.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/issue":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"post":{"operationId":"issueCompanyInvoice","tags":["CompanyInvoiceLifecycle"],"summary":"Issue an invoice","description":"Finalizes a draft invoice of this company: assigns its definitive number from the\nconfigured series and makes it immutable.\n\n- **Irreversible:** an issued invoice is corrected with a corrective invoice\n  (`POST …/{invoice_id}/corrective`) or voided (`POST …/{invoice_id}/void`), never edited.\n- **Asynchronous:** PDF generation and submission to the AEAT happen after the response,\n  so a `200` means the invoice was accepted for submission, not that the AEAT has\n  registered it. Use `wait_for_pdf` to wait for the PDF.\n- **Nothing to collect:** a `STANDARD` or `SIMPLIFIED` invoice whose `total_to_pay` is 0\n  is issued as `PAID`, with `payment_date` equal to `issue_date`. It is registered with\n  the AEAT like any other invoice, with a total of 0.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"wait_for_pdf","in":"query","required":false,"description":"If `true`, waits for PDF generation and returns the URL in the response. Adds ~1-2s of\nlatency but guarantees the PDF is immediately available.\n","schema":{"type":"boolean","default":false}},{"name":"attach_source_invoices","in":"query","required":false,"description":"Only applies when the invoice has automatic email sending enabled. If `true`, the\nemail sent after issuing also attaches a ZIP (`suplidos_<invoice-number>.zip`) with\nthe PDFs of the source invoices referenced by the invoice's SUPLIDO consolidation\nlines. Access to sources owned by managed accounts is re-checked with the same rules\nas issuing, and the request fails synchronously with an actionable error — never a\npartial ZIP — if the invoice has no consolidation sources\n(`ATTACH_SOURCE_INVOICES_NO_SOURCES`), a source is not reachable\n(`ATTACH_SOURCE_INVOICE_UNAVAILABLE`) or a source has no generated PDF\n(`ATTACH_SOURCE_PDF_MISSING`).\n","schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Invoice issued successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"invoice_issued_success":{"$ref":"#/components/examples/issue-success"}}}}},"400":{"description":"Cannot issue invoice (invalid status or incomplete data).\n\n`SERIES_NUMBER_COLLISION`: the number the series would assign is already used by another\ninvoice of the same company, in this series or in another one. Nothing is issued and no\nnumber is consumed, but retrying gives the same result: the series needs review, so\ncontact support.\n\nOther `error.code` values with this status:\n\n- `ATTACH_SOURCE_INVOICES_NO_SOURCES` — `attach_source_invoices` was requested but the invoice has no SUPLIDO consolidation lines with `source_invoice_ids`; drop the flag or add the source invoices.\n- `ATTACH_SOURCE_INVOICE_UNAVAILABLE` — a source invoice referenced by a SUPLIDO line no longer exists or is no longer accessible; send without `attach_source_invoices` or restore access.\n- `ATTACH_SOURCE_PDF_MISSING` — one or more source invoices have no generated PDF yet (their numbers are in `message`); wait for their PDFs and retry.\n- `ATTACH_SOURCE_ZIP_TOO_LARGE` — the ZIP of source-invoice PDFs exceeds the sendable limit (25 MB by default); send fewer sources or omit `attach_source_invoices`.\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Another request is issuing this same invoice. `error.code` is\n`CONCURRENT_MODIFICATION`; nothing was issued twice and no number was consumed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Invoice cannot be issued (validation failed), or the company/NIF is not ready to issue\nin this environment. In the latter case `error.code` is `EMISSION_NOT_READY` and\n`error.details.blockers[]` lists the reasons (`PROFILE_INCOMPLETE`, `COMPANY_NOT_ACTIVATED`, `ENV_MISMATCH`, `NIF_NOT_REGISTERED`,\n`NIF_REPRESENTATION_REQUIRED`). When a blocker is `PROFILE_INCOMPLETE`, `error.details.missing_fields[]` names which\nfields of the company's fiscal identity are still missing (`entity_type`,\n`legal_name`, `address`) — the same tokens the profile endpoints use.\nAt issue time the generated number must also fit the AEAT invoice number: more than 60\ncharacters answers `INVOICE_NUMBER_TOO_LONG`, a character outside printable ASCII or one\nof `\"`, `'`, `<`, `>`, `=` answers `INVOICE_NUMBER_INVALID_CHARACTERS`. In both cases the\ninvoice is not issued and the number is not used; issue it with another series, or fix the\nseries code or format while the series has no issued invoices, and retry.\nWhen the invoice is recorded with VeriFactu, its tax breakdown must also fit one AEAT\nrecord: lines are grouped by tax, regime key, operation type, exemption, tax rate and\nequivalence surcharge rate, and more than 12 different groups answers\n`INVOICE_TAX_BREAKDOWN_TOO_LONG`, and a tax or surcharge rate with more than two decimals\nanswers `INVOICE_TAX_RATE_TOO_MANY_DECIMALS`. AEAT requires the Macrodato flag on a record of\n100,000,000.00 EUR or more in absolute value; such an invoice, corrective invoices included,\nanswers `INVOICE_TOTAL_TOO_LARGE_FOR_VERIFACTU` and is not numbered.\nThe recipient's `legal_name` is recorded as it is on the invoice,\nso with VeriFactu it must be at most 120 characters: a longer one answers\n`FIELD_TOO_LONG`. BeeL. builds the record it will send before numbering and checks it against AEAT's validations: an\ninvoice with nothing but disbursements (`SUPLIDO` lines) is not an invoice and answers\n`INVOICE_REQUIRES_AT_LEAST_ONE_NORMAL_LINE`, and any other record AEAT would reject answers the specific\ncode or `VERIFACTU_RECORD_NOT_DECLARABLE`, with the AEAT section in its message. In all\nthese cases the invoice is not issued and the number is not used.\nA draft that still stores the 0.625 equivalence surcharge (0.62 since Royal Decree-law\n1/2023) answers `SURCHARGE_RATE_REQUIRES_RECALCULATION` and is not issued: edit the\ndraft so its lines are recalculated, and retry.\nThe fiscal rules on the lines are checked again here and answer `422` with their own\n`error.code`: `SIMPLIFICADA_FORBIDS_ISP`, `SIMPLIFICADA_FORBIDS_SURCHARGE` and\n`SIMPLIFICADA_FORBIDS_CROSS_BORDER` (a `SIMPLIFIED` invoice cannot carry reverse charge,\nan equivalence surcharge or an operation of art. 4.4 RD 1619/2012),\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` (the recipient lacks the identifier type the\n`exemption_reason` needs, for example after the customer lost its `NIF_IVA`),\n`EXEMPTION_NOT_FOR_ISSUED_INVOICE` (an exemption no longer accepted on a new invoice),\n`REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION` (a `regime_key` that requires an exemption the\nline does not carry) and `VAT_RATE_NOT_ACCEPTED_ON_DATE` (a VAT rate AEAT does not accept\non the operation date, whose message names the dates that rate is accepted).\n\nOther `error.code` values with this status:\n\n- `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` — with VeriFactu, a `NOT_REGISTERED` (07) recipient whose DNI or NIE the census identifies is not numbered; send it as `nif` (see `AlternativeIdentifier`). Type 07 is never used for a company, whose name is not verified.\n- `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.\n- `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.\n- `ONLY_DRAFT_EMITTABLE` — only a `DRAFT` can be issued; this invoice is already issued or in another state. Read it to see its number.\n- `OPERATION_DATE_AFTER_ISSUE_DATE` — `operation_date` is later than the issue date (today when issuing); send today or a past date.\n- `OPERATION_DATE_TOO_OLD` — `operation_date` is more than twenty years before the issue date; AEAT does not accept it. Send a more recent date.\n- `PROFORMA_NOT_ISSUABLE` — a proforma is never issued; convert it with `convert-to-invoice` (optionally with `issue: true`).\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/void":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"post":{"operationId":"voidCompanyInvoice","tags":["CompanyInvoiceLifecycle"],"summary":"Void an issued invoice","description":"Voids an issued invoice of this company. The document is kept and its number is never\nreused.\n\n- **When to use it:** the invoice was issued by mistake — the operation never took place,\n  it was a test, or it is an accidental duplicate. If the operation did take place but\n  the invoice is wrong, issue a corrective invoice instead\n  (`POST …/{invoice_id}/corrective`). The exception is a withholding that should not\n  have been applied: it is not a cause for a corrective, so void the invoice and issue a\n  new one without it.\n- **Sent or paid:** voiding an invoice that was already emailed to the customer or paid\n  requires `issued_in_error: true`, confirming it was issued by mistake; without it the\n  request fails with `422 VOID_REQUIRES_ISSUED_IN_ERROR`. What counts is the fact, not the\n  current `status`: an email in the invoice's sending history (moving it back from `SENT`\n  to `ISSUED` does not erase it), or a `paid_at` (it stays after the invoice is\n  `RECTIFIED`). Corrective invoices follow the same rule.\n- **Corrected invoices:** an invoice with live corrective invoices cannot be voided — it\n  was corrected, so the operation took place; issue another corrective\n  (`422 INVOICE_HAS_LIVE_CORRECTIVES`). A `TOTAL` corrective cannot be voided either: the\n  invoice it rectifies would stay voided with nothing to offset it\n  (`422 TOTAL_CORRECTIVE_NOT_VOIDABLE`).\n- **`reason`:** required, at least 10 characters — it is fiscal data.\n- **`void_date`:** deprecated. A date earlier than the invoice's issue date is rejected\n  with `422 VOID_DATE_BEFORE_ISSUE_DATE`.\n- **VeriFactu:** when it is enabled for the invoice, a cancellation record is submitted\n  to the AEAT.\n- **PDF:** unchanged. The PDF of the invoice stays the one that was delivered; the void is\n  reported by the invoice's `status` and the `invoice.voided` webhook.\n- **Proformas:** voiding an `ACTIVE` proforma is a plain status change with no fiscal\n  effect — no corrective invoice, nothing submitted to the AEAT. The voided proforma is\n  kept as the record of a rejected or withdrawn offer and stays listed.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInvoiceRequest"},"examples":{"void_by_error":{"$ref":"#/components/examples/void-issued-by-error"}}}}},"responses":{"200":{"description":"Invoice voided successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"The invoice cannot be voided in its current status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The invoice was already voided, or another request voided it at the same time.\n`error.code` is `INVOICE_ALREADY_VOIDED` or `CONCURRENT_MODIFICATION`; the invoice was\nvoided exactly once either way, so this is safe to treat as success.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `EXCHANGE_INVOICE_NOT_VOIDABLE` — the invoice replaced simplified invoices in an exchange; voiding it would drop those sales from the books. Correct it instead.\n- `INVOICE_HAS_LIVE_CORRECTIVES` — the invoice has live correctives, so the operation took place and cannot be voided; issue another corrective instead.\n- `TOTAL_CORRECTIVE_NOT_VOIDABLE` — a TOTAL corrective cannot be voided: its original would stay voided with nothing offsetting it. Issue a new invoice instead.\n- `VOID_DATE_BEFORE_ISSUE_DATE` — `void_date` is earlier than the invoice's issue date; an invoice cannot be voided before it exists.\n- `VOID_REQUIRES_ISSUED_IN_ERROR` — the invoice was already emailed to the customer or paid, whatever its current status; set `issued_in_error: true` to confirm it was issued by mistake, or correct it instead.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/simplified-exchanges":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanySimplifiedExchange","tags":["CompanyInvoiceLifecycle"],"summary":"Exchange simplified invoices for a full invoice","description":"Issues a full invoice in exchange for one or more simplified invoices already issued, when\nthe customer asks for an invoice with their details. It is not a corrective invoice: it\ndocuments the same operations again with the recipient identified (RD 1619/2012, art. 15.6).\n\n- **What it issues:** a `STANDARD` invoice with the lines of the simplified invoices and\n  the `recipient` sent, numbered in `series_id` or in the company's default standard series.\n  It lists the invoices it replaces in `replaced_invoice_ids`.\n- **The simplified invoices:** each becomes `VOIDED` with `void_cause` `EXCHANGED`, in the\n  same act: their records are not cancelled, the exchange replaces them. They must be\n  simplified invoices (`422 EXCHANGE_REQUIRES_SIMPLIFIED`), issued and not voided,\n  exchanged or corrected before (`422 SIMPLIFIED_NOT_EXCHANGEABLE`), and each one listed\n  once in `simplified_invoice_ids` (`422 EXCHANGE_DUPLICATED_SIMPLIFIED`, before anything\n  is read or numbered).\n- **The exchange invoice** cannot be voided afterwards (`422 EXCHANGE_INVOICE_NOT_VOIDABLE`),\n  and when it is recorded as `F3` it cannot be corrected yet (see the corrective operation).\n- **VeriFactu:** the exchange invoice is recorded as `F3`, identifying each simplified\n  invoice it replaces by number and issue date. Each of them must already be accepted by\n  the AEAT, or nothing is issued: one issued without VeriFactu fails with\n  `422 SIMPLIFIED_EXCHANGE_NOT_RECORDABLE`; one whose record is still pending fails with\n  `422 EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED` (wait until the AEAT accepts it and retry);\n  one whose record was rejected fails with `422 EXCHANGE_SIMPLIFIED_RECORD_REJECTED` (fix\n  or resubmit it first).\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSimplifiedExchangeRequest"},"examples":{"exchange_one_ticket":{"summary":"Exchange one simplified invoice for a full invoice","value":{"simplified_invoice_ids":["550e8400-e29b-41d4-a716-446655440010"],"recipient":{"customer_id":"8f1e2a3b-4c5d-6e7f-8091-a2b3c4d5e6f7"}}}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Full invoice issued in exchange for the simplified invoices","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"A simplified invoice in `simplified_invoice_ids` does not exist under this company.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `EXCHANGE_DUPLICATED_SIMPLIFIED` — the same id appears more than once in `simplified_invoice_ids`; list each simplified invoice once.\n- `EXCHANGE_REQUIRES_SIMPLIFIED` — an id in `simplified_invoice_ids` is not a simplified invoice; only simplified invoices can be exchanged for a full one.\n- `EXCHANGE_SIMPLIFIED_NOT_YET_ACCEPTED` — a simplified invoice's VERI*FACTU registration is still pending; retry once the AEAT has accepted it.\n- `EXCHANGE_SIMPLIFIED_RECORD_REJECTED` — the AEAT rejected a simplified invoice's registration; fix or resubmit that record before exchanging it.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFIED_EXCHANGE_NOT_RECORDABLE` — the exchange is registered in VERI*FACTU but a simplified invoice was issued without it, so the AEAT has no record to replace.\n- `SIMPLIFIED_NOT_EXCHANGEABLE` — a simplified invoice is not issued and live: it is a draft, voided, already exchanged, has live correctives, or belongs to another company.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/corrective":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"post":{"operationId":"createCompanyCorrectiveInvoice","tags":["CompanyInvoiceLifecycle"],"summary":"Create a corrective invoice","description":"Issues a corrective invoice that amends the invoice in the path. It is a new fiscal\ndocument with its own number, not an edit of the original.\n\n- **`rectification_type`:** `TOTAL` leaves the original `VOIDED` and rectifies what is\n  still invoiced on it: every line of the original and of its live correctives (voided ones\n  do not count), negated. It takes no `lines` — sending them fails with\n  `422 RECTIFICATIVA_TOTAL_CON_LINEAS`. `PARTIAL` leaves the original `RECTIFIED` and\n  requires the adjustment `lines`.\n- **Never more than was invoiced:** a `PARTIAL` may raise any amount, but may not take the\n  taxable base of any rate (tax, rate and equivalence surcharge) below zero once the\n  previous correctives are counted. That fails with\n  `422 CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`, and `error.details` (`CorrectiveInvoiceErrorDetails`)\n  carries `tax_group` (for example `IVA 21%`) and `max_reduction`, how much of that rate is\n  left to rectify. A\n  `TOTAL` on an invoice that previous correctives already brought to zero fails with\n  `422 CORRECTIVE_NOTHING_LEFT_TO_RECTIFY`.\n- **No disbursements in a `PARTIAL`:** its lines are `NORMAL` only. A disbursement\n  (`SUPLIDO`) is outside the taxable base, so there is nothing in it to rectify: a\n  `SUPLIDO` line fails with `422 CORRECTIVE_PARTIAL_SUPLIDO_NOT_ALLOWED`. If an amount\n  invoiced as a disbursement was not one, rectify it as a `NORMAL` line with its tax. A\n  `TOTAL` carries the original's `SUPLIDO` lines, negated, as they were.\n- **Not for the withholding alone:** a `PARTIAL` whose lines leave the taxable base of\n  every rate unchanged and only change the withholding fails with\n  `422 CORRECTIVE_WITHHOLDING_ONLY`. A withholding is not a cause for a corrective: void\n  the invoice and issue a new one without it.\n- **The original's PDF:** unchanged by either type. The corrective has its own PDF; the\n  original keeps the one that was delivered, and its new status is in `status`.\n- **Total of 0:** a corrective whose `total_to_pay` is 0 has nothing to refund or\n  collect, so it is issued as `PAID`, with `payment_date` equal to `issue_date`.\n- **What can be rectified:** an ordinary or simplified invoice in `ISSUED`, `SENT`,\n  `PAID`, `OVERDUE` or `RECTIFIED`. Rectifying a corrective fails with\n  `422 CORRECTIVE_NOT_RECTIFIABLE` — to fix an erroneous corrective, issue another one\n  against the original invoice.\n- **Repeat rectifications:** several `PARTIAL` correctives are allowed, but a `VOIDED`\n  invoice is no longer rectifiable, so a second `TOTAL` against the same invoice fails\n  with `422 INVOICE_NOT_CORRECTIBLE_IN_CURRENT_STATUS`.\n- **Correcting the recipient's data:** when the invoice recorded its recipient with a wrong\n  name, tax ID or address, send the corrected `recipient` with `rectification_type`\n  `PARTIAL`, `rectification_code` `R4` and no `lines`. The corrective carries the corrected\n  recipient and does not change the amounts: its lines negate what is still invoiced and\n  repeat it, so every rate nets to zero. A different person is not a data correction\n  (`422 CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON`): correct the invoice in full and issue a new\n  one to the right customer.\n- **Original rejected by the AEAT:** when VeriFactu rejected the original's record and it\n  has not been resubmitted, the original is not in the AEAT's books: fix and resubmit it\n  first. Until then the request fails with `422 CORRECTIVE_ORIGINAL_RECORD_REJECTED`.\n- **Exchange invoice recorded as F3:** correcting a full invoice issued in exchange for\n  simplified invoices is not available yet: `422 EXCHANGE_INVOICE_NOT_CORRECTABLE`.\n  Contact support.\n- **Deadline:** four years from when the tax accrued (the original's operation date) or,\n  for a cause of article 80 of the VAT Act, from the `circumstance_date` you declare. Past\n  it the request fails with `422 CORRECTIVE_OUT_OF_TIME`, with `deadline` and\n  `counted_from` in `error.details` (`CorrectiveInvoiceErrorDetails`).\n- **What the reason code requires** (Ley 37/1992, art. 80): `R2` (insolvency) and `R3` (bad\n  debt) need a recipient established in Spain, the Canary Islands, Ceuta or Melilla —an\n  `R2` also accepts a recipient in another EU member state, for insolvency proceedings\n  there— and fail otherwise with `422 CORRECTIVE_RECIPIENT_NOT_ESTABLISHED`. An `R3` needs\n  at least six months since the original's operation date\n  (`422 CORRECTIVE_BAD_DEBT_TOO_EARLY`, with `earliest_date`; one year when the previous\n  year's turnover exceeded 6,010,121.04 €, which is the issuer's to apply), and on an operation\n  with a base of 50 € or less it needs `recipient_is_business`\n  (`422 CORRECTIVE_BAD_DEBT_BASE_TOO_LOW`). The other conditions of each code (claims,\n  guarantees, related parties, filing with the AEAT) are the issuer's to meet.\n- **Fiscal inheritance on a `PARTIAL`:** a line that omits `irpf_rate` or\n  `equivalence_surcharge_rate` takes it from the **original invoice** — the document\n  being amended — and never from the company's current tax profile, so a profile that\n  changed after the original was issued does not leak into the credit note. An explicit\n  value always wins, `0` included. The surcharge inherits the *regime* (on/off), not the\n  rate: the rate is re-derived from each corrective line's own VAT (21→5.2, 10→1.4,\n  5→0.62, 4→0.5), and an original outside the regime pins the line to `0`. The\n  original's `SUPLIDO` lines are left out of that inheritance. When the original is not unambiguous BeeL does not\n  pick for you: different IRPF rates per line fail with\n  `422 CORRECTIVE_ORIGINAL_MIXED_IRPF`, and a surcharge applied on some lines but not\n  others fails with `422 CORRECTIVE_ORIGINAL_MIXED_SURCHARGE`. Declare the figure on every\n  line to get past either — both only fire when some line actually needs to inherit.\n- **`series_id`:** when omitted, the document is numbered in the company's default\n  corrective series, never in the series of the original: corrective invoices go in a\n  series of their own (RD 1619/2012, art. 6.1.a). If the company has none, it is created\n  on first use (code `R`, or the next free one that cannot repeat another series'\n  numbers). An explicit `series_id` must be a corrective series.\n- **Numbering conflict:** if the number the series would assign is already used by another\n  invoice of the same company, in this series or in another one, the request fails with\n  `400 SERIES_NUMBER_COLLISION` without issuing anything or consuming a number. The series\n  needs review, so contact support.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCorrectiveInvoiceRequest"},"examples":{"corrective_total_r1":{"$ref":"#/components/examples/corrective-total-r1"},"corrective_total_cancellation_r1":{"$ref":"#/components/examples/corrective-total-cancellation-r1"},"corrective_partial_r2":{"$ref":"#/components/examples/corrective-partial-r2"},"corrective_partial_r3":{"$ref":"#/components/examples/corrective-partial-r3"},"corrective_r1_partial_discount":{"$ref":"#/components/examples/r1-partial-discount"},"corrective_recipient_data_r4":{"$ref":"#/components/examples/recipient-data-r4"},"corrective_r5_partial_simplified_return":{"$ref":"#/components/examples/r5-partial-simplified-return"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Corrective invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"corrective_invoice_created_success":{"$ref":"#/components/examples/corrective-created-success"}}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `CORRECTIVE_ORIGINAL_NOT_FOUND` — the invoice in the path does not exist, was deleted, or belongs to another company or environment than the request.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error, or the company/NIF is not ready to issue in this environment\n(`error.code` `EMISSION_NOT_READY`). A `PARTIAL` corrective whose lines leave a\nfiscal figure undeclared also lands here when the original invoice is ambiguous:\n`CORRECTIVE_ORIGINAL_MIXED_IRPF` (different IRPF rates per line) and\n`CORRECTIVE_ORIGINAL_MIXED_SURCHARGE` (equivalence surcharge on some lines only).\nA `rectification_code` that does not fit the original invoice is rejected too:\n`RECTIFICATIVA_R5_ONLY_SIMPLIFICADA` (`R5` against an invoice that is not simplified)\nand `RECTIFICATIVA_R1R4_NOT_SIMPLIFICADA` (`R1`–`R4` against a simplified invoice).\nAn `R5` corrective of a simplified invoice cannot introduce what the simplified invoice\ncould not carry: reverse charge (`SIMPLIFICADA_FORBIDS_ISP`), an equivalence surcharge\n(`SIMPLIFICADA_FORBIDS_SURCHARGE`), IRPF withholding (`SIMPLIFICADA_FORBIDS_IRPF`) or an\noperation of art. 4.4 RD 1619/2012 the original did not already have\n(`SIMPLIFICADA_FORBIDS_CROSS_BORDER`).\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\n\nOther `error.code` values with this status:\n\n- `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` — with VeriFactu, a `NOT_REGISTERED` (07) recipient whose DNI or NIE the census identifies is not numbered; send it as `nif` (see `AlternativeIdentifier`). Type 07 is never used for a company, whose name is not verified.\n- `CORRECTIVE_BAD_DEBT_BASE_TOO_LOW` — an `R3` (bad debt) on an operation with a taxable base of 50 € or less needs `recipient_is_business: true`; without it the reduction is not allowed.\n- `CORRECTIVE_BAD_DEBT_TOO_EARLY` — an `R3` (bad debt) is not allowed until six months after the operation date; retry from `details.earliest_date`.\n- `CORRECTIVE_CIRCUMSTANCE_DATE_NOT_APPLICABLE` — `circumstance_date` was sent with `R4`, which is not an article 80 cause; drop the field or use the right code.\n- `CORRECTIVE_CIRCUMSTANCE_DATE_OUT_OF_RANGE` — `circumstance_date` is before the original's operation date or in the future; send a date between the two.\n- `CORRECTIVE_EXCEEDS_INVOICED_AMOUNT` — the PARTIAL lines take a tax rate's base below zero; `details.max_reduction` is what can still be reduced for `details.tax_group`.\n- `CORRECTIVE_NOTHING_LEFT_TO_RECTIFY` — earlier correctives already brought the original's balance to zero, so a TOTAL corrective (or a recipient-data correction) has nothing left to rectify.\n- `CORRECTIVE_NOT_RECTIFIABLE` — the target is itself a corrective invoice; correct the original standard or simplified invoice with a new corrective instead.\n- `CORRECTIVE_ORIGINAL_RECORD_REJECTED` — the AEAT rejected the original's registration and it has not been fixed; resubmit or correct that record before issuing a corrective.\n- `CORRECTIVE_PARTIAL_SUPLIDO_NOT_ALLOWED` — a PARTIAL line has `line_type: SUPLIDO`; a disbursement is outside the taxable base and is not rectified. If the amount was not a disbursement, send it as a `NORMAL` line with its tax.\n- `CORRECTIVE_OUT_OF_TIME` — more than four years have passed since the operation date or `circumstance_date` (`details.counted_from`); the last valid day was `details.deadline`.\n- `CORRECTIVE_RECENT_DUPLICATE` — an identical corrective of this invoice was issued under two minutes ago; retry with the same `Idempotency-Key` instead of resending.\n- `CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON` — the corrected `recipient` is a different customer; void with a TOTAL corrective and issue a new invoice to the right one.\n- `CORRECTIVE_RECIPIENT_NOT_ACCEPTED` — `recipient` is only accepted in a PARTIAL `R4` without `lines` (recipient-data correction); omit it otherwise and the original's recipient is used.\n- `CORRECTIVE_RECIPIENT_NOT_ESTABLISHED` — `R2`/`R3` require a recipient established in Spain (`R2` also accepts one in the EU); use another code if that is not the case.\n- `CORRECTIVE_RECIPIENT_UNCHANGED` — the corrected `recipient` has the same name, tax ID and address as recorded, so there is nothing to correct.\n- `CORRECTIVE_WITHHOLDING_ONLY` — the PARTIAL lines only change the withholding (IRPF), which is not a cause for a corrective; void the invoice and issue a new one.\n- `EXCHANGE_INVOICE_NOT_CORRECTABLE` — the invoice replaced simplified invoices and was registered as F3; it cannot be corrected with `R1`–`R4`.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_INVALID_QUANTITY` — `quantity` is 0; send a non-zero quantity (negative values are allowed for discounts or franchises).\n- `LINE_MAIN_TAX_REQUIRED` — a `NORMAL` line has no `main_tax`; it is never defaulted from the company settings, so send it explicitly.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `PROFORMA_CORRECTIVE_FORBIDDEN` — a proforma is not a fiscal document and cannot be corrected; void it or edit it while active.\n- `RECTIFICATION_REASON_REQUIRED` — `reason` is blank (only whitespace); explain why the invoice is being corrected.\n- `RECTIFICATIVA_PARCIAL_SIN_LINEAS` — a PARTIAL corrective needs `lines` with the adjustment, unless it only corrects the recipient's data (`R4` with `recipient`).\n- `RECTIFICATIVA_TOTAL_CON_LINEAS` — a TOTAL corrective takes no `lines`: it negates what is still invoiced on the original. Remove `lines` or use PARTIAL.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"incompatible_series":{"$ref":"#/components/examples/incompatible_series"},"original_mixed_irpf":{"$ref":"#/components/examples/original_mixed_irpf"},"original_mixed_surcharge":{"$ref":"#/components/examples/original_mixed_surcharge"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"post":{"operationId":"convertCompanyProformaToInvoice","tags":["CompanyProforma"],"summary":"Convert a proforma into an invoice","description":"Converts an accepted proforma of this company into a real invoice. The new invoice is\ncreated as a `STANDARD` draft linked back through `source_proforma_id`.\n\n- **What converts:** only proformas in status `ACTIVE`. One shown as `EXPIRED` is still\n  `ACTIVE` underneath and converts too.\n- **The proforma:** preserved as the record of what the customer accepted — it keeps its\n  `PRO-...` number and PDF and moves to the terminal status `CONVERTED`.\n- **`issue`:** with `true` the new invoice is numbered and issued in the same atomic\n  call. If issuing fails nothing is created and the proforma stays `ACTIVE`. That\n  includes `400 SERIES_NUMBER_COLLISION`: the number the series would assign is already\n  used by another invoice of the same company, and the series needs review, so contact\n  support.\n- **Errors:** `422 CONVERSION_REQUIRES_PROFORMA` when the document is not a proforma,\n  `422 PROFORMA_NOT_CONVERTIBLE` when it is not `ACTIVE`, and\n  `409 PROFORMA_ALREADY_CONVERTED` when it has already been converted — a second call\n  never creates a second invoice.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertProformaToInvoiceRequest"},"examples":{"convert_proforma_to_draft":{"$ref":"#/components/examples/convert-proforma-draft"},"convert_proforma_and_issue":{"$ref":"#/components/examples/convert-proforma-issue"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Invoice created successfully from the proforma (draft, or emitted when `issue` is true)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"The document is not a proforma (`error.code` `CONVERSION_REQUIRES_PROFORMA`), or its\nstatus does not allow conversion (`PROFORMA_NOT_CONVERTIBLE`), or another validation\nerror, or the company/NIF is not ready to issue in this environment\n(`EMISSION_NOT_READY`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n- `VAT_RATE_NOT_ACCEPTED_ON_DATE` — AEAT only accepts that VAT rate for operations in its period (e.g. 5 % until 2024-09-30); send the matching `operation_date` or current rate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/status":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"put":{"operationId":"setCompanyInvoiceStatus","tags":["CompanyInvoiceLifecycle"],"summary":"Set the status of an invoice","description":"Sets the commercial status of an invoice. Any transition other than the ones below is\nrejected.\n\n- **`PAID`:** from `ISSUED`, `SENT` or `OVERDUE`.\n- **`SENT`:** from `ISSUED`.\n- **`ISSUED`:** from `SENT` only, to undo a `SENT` set by mistake.\n- **Not set here:** issuing and voiding are fiscal acts with their own operations\n  (`POST …/{invoice_id}/issue`, `POST …/{invoice_id}/void`), and issuing is never undone.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetInvoiceStatusRequest"},"examples":{"mark_invoice_paid":{"$ref":"#/components/examples/mark-paid"}}}}},"responses":{"200":{"description":"Status applied successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]},"examples":{"invoice_marked_paid_success":{"$ref":"#/components/examples/mark-paid-success"}}}}},"400":{"description":"The invoice does not allow this transition from its current status\n\nOther `error.code` values with this status:\n\n- `MARK_SENT_ALREADY_SENT` — the invoice is already `SENT`; nothing to do.\n- `MARK_SENT_FROM_DRAFT_NOT_ALLOWED` — the invoice is still a `DRAFT`; issue it first, then mark it as sent.\n- `MARK_SENT_FROM_INVALID_STATE` — only an `ISSUED` invoice can be marked as sent; this one is scheduled, or is a proforma (active or already invoiced).\n- `MARK_SENT_FROM_LATER_STATE` — the invoice is already past `SENT` (paid, overdue, voided or corrected); its status cannot go back.\n- `PROFORMA_PAYMENT_FORBIDDEN` — a proforma does not take payments; convert it to an invoice and mark that one as paid.\n- `STATUS_NOT_MODIFIABLE` — the invoice's current status does not allow this change: only drafts, scheduled invoices and active proformas can be edited, and only issued, sent or overdue invoices can be marked paid.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `PROFORMA_TRANSITION_FORBIDDEN` — a proforma only moves between active, invoiced and voided; the requested status belongs to fiscal invoices.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoices/{invoice_id}/schedule":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"invoice_id","in":"path","required":true,"description":"Invoice ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanyInvoiceSchedule","tags":["CompanyInvoiceLifecycle"],"summary":"Get the scheduling of an invoice","description":"Returns the date and generation mode currently scheduled for this invoice. An invoice with\nno scheduling answers `404`, since the sub-resource does not exist yet. To move only the\ndate, read the current `generation_mode` here and send it back on the `PUT`.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Scheduling retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceScheduleResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The invoice does not exist (`INVOICE_NOT_FOUND`), or it is not scheduled (`INVOICE_NOT_SCHEDULED`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"operationId":"setCompanyInvoiceSchedule","tags":["CompanyInvoiceLifecycle"],"summary":"Schedule an invoice","description":"Replaces the scheduling of a draft invoice, whether it had one or not, moving it to\n`SCHEDULED`. Both fields of the body are required.\n\n- **`scheduled_for`:** the date the invoice is processed on. Today or later; an earlier\n  date is rejected with `422 SCHEDULED_DATE_IN_PAST`.\n- **`generation_mode`:** `DRAFT` leaves the invoice as a draft for manual review,\n  `ISSUE_AND_SEND` issues and sends it automatically. There is no default.\n- **Availability:** requires the `scheduled_invoices` feature.\n- **`If-None-Match: *`:** without it, this call always replaces the scheduling whole — the\n  behaviour integrators rely on. Send it to opt into the opposite: if the invoice is\n  **already** scheduled, nothing is changed and the call fails with\n  `412 INVOICE_ALREADY_SCHEDULED` instead of silently overwriting the date and mode someone\n  else set. Any other value, or omitting the header, keeps today's replace-whole behaviour.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"If-None-Match","in":"header","required":false,"description":"Only `*` has meaning here (RFC 9110 §13.1.2: \"any current representation\"). Send it to\nrequire that the invoice is **not** already scheduled — see the operation description.\nAny other value is ignored: this operation has no representation to compare an ETag\nagainst, so a list of ETags never matches and the call goes ahead as if the header\nhad not been sent.\n","schema":{"type":"string"},"example":"*"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetInvoiceScheduleRequest"},"examples":{"set_invoice_schedule_draft":{"$ref":"#/components/examples/schedule-draft"},"set_invoice_schedule_issue_and_send":{"$ref":"#/components/examples/schedule-issue-and-send"}}}}},"responses":{"200":{"description":"Scheduling applied successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"400":{"description":"The invoice cannot be scheduled, for one of two reasons:\n\n- `INVOICE_STATUS_NOT_SCHEDULABLE` — it is in a status that cannot be scheduled:\n  already issued, voided or corrected.\n- `PROFORMA_SCHEDULE_FORBIDDEN` — it is a proforma. Scheduling is not part of the\n  proforma lifecycle: a proforma is not a fiscal document and issues nothing on a\n  date. Convert it to an invoice first, then schedule the resulting draft.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"412":{"description":"`INVOICE_ALREADY_SCHEDULED` — the request carried `If-None-Match: *` and the invoice\nwas already scheduled. Nothing was changed: its `scheduled_for` and `generation_mode`\nare still whatever they were before this call. Read the current scheduling with\n`GET …/schedule` and, if the change is still wanted, repeat the `PUT` without the\nheader to replace it explicitly.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error. A missing `generation_mode` is rejected here: there is no default,\nbecause silently falling back to `DRAFT` would downgrade an `ISSUE_AND_SEND` and the\ninvoice would never be issued. `SCHEDULED_DATE_IN_PAST` when `scheduled_for` is before\ntoday, with the rejected value in `error.details.scheduled_for`.\n\nOther `error.code` values with this status:\n\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyInvoiceSchedule","tags":["CompanyInvoiceLifecycle"],"summary":"Update the scheduling of an invoice","description":"Updates only the fields present in the body of the scheduling of an invoice that is\n**already** `SCHEDULED` — same convention as `PATCH …/{invoice_id}` on the invoice\nitself. Unlike the `PUT`, this never creates a scheduling: an invoice that is not\nscheduled answers `404`, it is not programmed by this call.\n\n- **`scheduled_for`:** moves the date. If `generation_mode` is not sent, the mode\n  already scheduled is kept.\n- **`generation_mode`:** changes the mode. If `scheduled_for` is not sent, the date\n  already scheduled is kept.\n- At least one of the two fields is required; an empty body is rejected with `422`.\n- **Availability:** requires the `scheduled_invoices` feature, same as the `PUT`.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchInvoiceScheduleRequest"}}}},"responses":{"200":{"description":"Scheduling updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invoice"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The invoice does not exist, or it is not scheduled — this `PATCH` never creates a\nscheduling, same code as the `GET`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error: `SCHEDULE_PATCH_EMPTY` when the body carries neither field, and\n`SCHEDULED_DATE_IN_PAST` when `scheduled_for` is before today, with the rejected\nvalue in `error.details.scheduled_for`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyInvoiceSchedule","tags":["CompanyInvoiceLifecycle"],"summary":"Remove the scheduling of an invoice","description":"Removes the scheduling of an invoice, returning it to a plain draft. Idempotent: an invoice\nthat is not scheduled answers `204` all the same. Unlike the `PUT`, it does not require the\n`scheduled_invoices` feature.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"204":{"description":"The invoice is not scheduled"},"400":{"description":"The invoice is already issued, so there is no scheduling to remove (`error.code`\n`INVOICE_STATUS_NOT_SCHEDULABLE`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices":{"post":{"tags":["RecurringInvoices"],"summary":"Create a recurring invoice","description":"**Deprecated.** Use `POST /v1/companies/{company_id}/recurring-invoices`, which behaves\nidentically.\n\nCreates a recurring invoice template: the invoice data it repeats (lines, recipient,\nseries, payment) plus the recurrence that drives it.\n\n- **Cadence:** `frequency` is how often it generates — every 1 (`MONTHLY`), 3\n  (`QUARTERLY`) or 12 (`YEARLY`) months — on `day_of_month`, from `start_date` until\n  `end_date` if one is given. Omitted, `MONTHLY` applies.\n- **The cadence governs the step, not the first invoice:** the first occurrence is the\n  first `day_of_month` on or after `start_date`, found one month at a time whatever the\n  cadence; the cadence takes over from there. A `YEARLY` template starting 15 February\n  with `day_of_month` 10 first invoices on 10 March, then every 10 March after that — it\n  does not wait a year.\n- **`start_date` in the past:** accepted and stored as sent, but it never anchors\n  generation backwards. `next_generation` becomes the first date of the template's own\n  calendar on or after today (today included) — the grid of `day_of_month` dates\n  anchored at `start_date`, one every `frequency` — so on a quarterly or yearly\n  template it can land months from now, not this month. The missed periods are not\n  generated. If today's period was already invoiced in the system you are migrating\n  from, send a `start_date` of tomorrow (or of the next period) so it is not invoiced\n  twice.\n- **`draft_in_advance`:** whether the invoice is created as a draft for review before it\n  is emitted. The window is fixed at 5 days, and both options emit on the scheduled day.\n  Generating manually a period whose scheduled date is today or earlier emits it on the\n  spot. Omitted, no review draft is prepared. It supersedes the deprecated `preview_days`.\n- **VeriFactu:** the template does not carry it. Whether each generated invoice is\n  registered with AEAT is decided when that invoice is issued, against the company's\n  regime at that moment.\n\n**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.\n","operationId":"createRecurringInvoice","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/recurring-invoices"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecurringInvoiceRequest"}}}},"responses":{"201":{"description":"Recurring invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"Branch on `error.code`:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error.\nA line whose `exemption_reason` requires the recipient to carry a specific alternate\nidentifier type (today, `EXENTA_ART_25` needs `NIF_IVA`) is rejected with `error.code`\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` when the customer referenced by `customer_id`\ndoes not carry it. No template is created — the same rejection `POST /v1/invoices`\ngives for the same case, brought forward to the write instead of every generated\ninvoice bouncing at the AEAT.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAlso: `frequency` and `end_date` can each be valid on their own and still be\nimpossible together, when the window between `start_date` and `end_date` holds no\noccurrence at all — a `YEARLY` template covering a calendar year, created with the year\nalready under way, is the everyday case. That is rejected with `error.code`\n`RECURRING_NO_OCCURRENCE_IN_WINDOW`, and its `error.details` hangs off `end_date`,\nthe field you can move; the other lever is `frequency`. It is not the same as\n`RECURRING_END_DATE_BEFORE_START`, which says the *next* generation of a template that\nalready had one falls past its end: this one says there is no first invoice, and never\nwill be. Retrying does not help — widen `end_date` or shorten `frequency`.\nAlso: `series_id` (explicit or resolved by default) numbering against a series that is\ninactive is rejected with `error.code` `SERIES_INACTIVE` — no template is created.\nActivate the series, or send a `series_id` for one that is already active.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `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`.\n- `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.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.\n- `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`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"get":{"tags":["RecurringInvoices"],"summary":"List recurring invoices","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/recurring-invoices`, which returns the\nsame list with the same filters.\n\nLists the recurring invoice templates of the authenticated user, with filters and\npagination.\n\n- **Filters:** `status` and `customer_id`, plus `page` and `limit` for pagination.\n- **Sorting:** `sort_by` and `sort_order`. The legacy aliases `sortBy`/`sortOrder` are\n  still honoured here but are not carried over to the canonical route.\n\n**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.\n","operationId":"listRecurringInvoices","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/recurring-invoices"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/RecurringInvoiceStatus"}},{"name":"customer_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"sort_by","in":"query","required":false,"description":"Field to sort by. Defaults to `created_at` when omitted.\n\nThis parameter was previously named `sortBy`. The old name is still accepted for\nbackwards compatibility (see `sortBy` below) and will be withdrawn in a future major\nversion — send `sort_by`.\n","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortBy"}},{"name":"sortBy","in":"query","required":false,"deprecated":true,"description":"**Deprecated** — former name of `sort_by`, still honoured so existing integrations keep\nworking. Ignored when `sort_by` is also present. Use `sort_by`.\n","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortBy"}},{"name":"sort_order","in":"query","required":false,"description":"Sort direction. Defaults to `desc` when omitted.\n\nThis parameter was previously named `sortOrder`. The old name is still accepted for\nbackwards compatibility (see `sortOrder` below) and will be withdrawn in a future major\nversion — send `sort_order`.\n","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortOrder"}},{"name":"sortOrder","in":"query","required":false,"deprecated":true,"description":"**Deprecated** — former name of `sort_order`, still honoured so existing integrations\nkeep working. Ignored when `sort_order` is also present. Use `sort_order`.\n","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortOrder"}}],"responses":{"200":{"description":"Recurring invoices list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["recurring_invoices"],"properties":{"recurring_invoices":{"type":"array","items":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}":{"get":{"tags":["RecurringInvoices"],"summary":"Get a recurring invoice","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which behaves identically.\n\nRetrieves the full details of a recurring invoice template, including its schedule,\ntemplate lines and next generation date.\n\n**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.\n","operationId":"getRecurringInvoice","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Recurring invoice retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["RecurringInvoices"],"summary":"Replace a recurring invoice","description":"**Deprecated.** The canonical form has a single update verb,\n`PATCH /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which is not\na drop-in replacement: it changes only the fields present in the body. To reproduce a\ntotal replacement, send every field and pass `null` in the ones you want cleared.\n\nReplaces the schedule, template lines and recipient of a recurring invoice with the body\nyou send. Only allowed while the template is active or paused.\n\n- **Not a partial update:** leaving out `end_date`, `payment_method` (with its\n  `payment_iban`, `payment_swift` and `payment_term_days`), `notes` or\n  `email_configuration` clears them.\n- **`lines`:** replaced as a whole. Omitting them or sending `null` keeps the current\n  ones, and an empty array is rejected.\n\n**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.\n","operationId":"updateRecurringInvoice","deprecated":true,"x-deprecation":"nif-migration","x-no-successor":"The canonical form only has PATCH, which leaves absent fields untouched instead of clearing them: it is not a drop-in destination for this route. Migration guidance is in the description — send every field and pass null in the ones you want cleared.","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRecurringInvoiceRequest"}}}},"responses":{"200":{"description":"Recurring invoice updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"Branch on `error.code`:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAlso: changing `frequency` is checked against the window. If the new cadence leaves\nno occurrence at all between `start_date` and `end_date`, the update is rejected with\n`error.code` `RECURRING_NO_OCCURRENCE_IN_WINDOW`, whose `error.details` hangs off\n`end_date` — the field you can move; the other lever is `frequency` itself. Nothing is\nwritten, so the template keeps the cadence and the `next_generation` it had. Shortening\n`end_date` without touching `frequency` is a different case and not this check: that\nstill answers `RECURRING_END_DATE_BEFORE_START` when the end falls before the next\ngeneration.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `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`.\n- `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.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.\n- `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.\n- `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`.\n- `RECURRING_START_DATE_NOT_EDITABLE` — the template already generated invoices, so `start_date` cannot change. Resend the current value or leave it out.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["RecurringInvoices"],"summary":"Update a recurring invoice","description":"**Deprecated.** Use\n`PATCH /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which behaves\nidentically.\n\nUpdates only the fields present in the body, leaving every other field of the recurring\ninvoice template as it is.\n\n- **Omitted vs `null`:** an omitted field keeps its current value; a field sent as `null`\n  is cleared, and only where the request schema documents the field as nullable.\n- **`lines`:** replaced as a whole, not patched line by line. The recipient survives the\n  change, and an empty array is rejected.\n- **`payment_method`:** replaced as a whole together with `payment_iban`, `payment_swift`\n  and `payment_term_days` — send them in the same request or they are dropped.\n- **Schedule:** `frequency`, `day_of_month` and `start_date` stay put unless you send\n  them; sending a new value for any of the three moves the next generation — resending\n  the ones already in effect changes nothing. Changing `frequency` recalculates it on\n  the new grid and discards a pending skip. `start_date` is only editable while the\n  template has not generated any invoice yet.\n\n**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.\n","operationId":"patchRecurringInvoice","x-successor":{"method":"PATCH","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchRecurringInvoiceRequest"}}}},"responses":{"200":{"description":"Recurring invoice updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error.\nThe update is judged on the resulting template, not only on the fields the request\ntouches: if a line ends up with an `exemption_reason` that requires the recipient to\ncarry a specific alternate identifier type (today, `EXENTA_ART_25` needs `NIF_IVA`) —\nwhether `customer_id` changed in this same request or the line did — and the customer\ndoes not carry it, the update is rejected with `error.code`\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` and nothing is written; the template keeps the\ncustomer and lines it had.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAlso: changing `frequency` is checked against the window. If the new cadence leaves\nno occurrence at all between `start_date` and `end_date`, the update is rejected with\n`error.code` `RECURRING_NO_OCCURRENCE_IN_WINDOW`, whose `error.details` hangs off\n`end_date` — the field you can move; the other lever is `frequency` itself. Nothing is\nwritten, so the template keeps the cadence and the `next_generation` it had. Shortening\n`end_date` without touching `frequency` is a different case and not this check: that\nstill answers `RECURRING_END_DATE_BEFORE_START` when the end falls before the next\ngeneration.\nAlso: the update is judged on the resulting template, not only on the fields the\nrequest touches. If the template's series ends up inactive — its current one, left\nuntouched, or one sent in this same request — the update is rejected with `error.code`\n`SERIES_INACTIVE` and nothing is written. Activate the series, or send a `series_id`\nfor one that is already active.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `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`.\n- `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.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.\n- `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.\n- `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`.\n- `RECURRING_START_DATE_NOT_EDITABLE` — the template already generated invoices, so `start_date` cannot change. Resend the current value or leave it out.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["RecurringInvoices"],"summary":"Delete a recurring invoice","description":"**Deprecated.** Use `DELETE /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}`, which behaves identically.\n\nPermanently deletes a recurring invoice template and cancels any pending scheduled\ngenerations. Invoices already generated from it are not affected.\n\n**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.\n","operationId":"deleteRecurringInvoice","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Recurring invoice deleted successfully"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/pause":{"post":{"tags":["RecurringInvoices"],"summary":"Pause a recurring invoice","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status` with `{\"status\": \"PAUSED\"}`, which behaves identically.\n\nStops automatic generation, keeping the schedule configuration intact.\n\n**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.\n","operationId":"pauseRecurringInvoice","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Recurring invoice paused successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECURRING_STATE_TRANSITION_INVALID` — the template cannot move from its current state to the requested one (e.g. a `COMPLETED` template never changes again). `error.details` carries `current_status` and `target_status`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/resume":{"post":{"tags":["RecurringInvoices"],"summary":"Resume a paused recurring invoice","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status` with `{\"status\": \"ACTIVE\"}`, which behaves identically.\n\nResumes automatic generation of a paused template. It keeps the scheduled next generation\ndate whenever that date has not fallen due yet (including today), so resuming never\nre-issues a period you already invoiced and never undoes a skipped one. Only a date left\nin the past is rescheduled, to the first occurrence after today; the periods missed while\nthe template was paused are not backfilled.\n\n**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.\n","operationId":"resumeRecurringInvoice","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Recurring invoice resumed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECURRING_STATE_TRANSITION_INVALID` — the template cannot move from its current state to the requested one (e.g. a `COMPLETED` template never changes again). `error.details` carries `current_status` and `target_status`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/generate":{"post":{"tags":["RecurringInvoices"],"summary":"Generate an invoice now from a recurring template","description":"**Deprecated.** Use\n`POST /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/generate`,\nwhich behaves identically.\n\nRuns the generation of a recurring template immediately, out of its schedule.\n\n- **It brings the upcoming occurrence forward, it does not add one:** the call consumes\n  the period that was pending, so the invoice is created now and `next_generation`\n  advances one period.\n- **`next_generation` in the response:** the template's next date after this call, or\n  `null` when the advance took the template past its `end_date` and its status is now\n  `COMPLETED`.\n- **Templates with `draft_in_advance`:** when the pending period's scheduled date is\n  today or earlier, the invoice is issued on the spot, dated today. When it is still\n  ahead, the call creates the review draft with the scheduled date.\n\n**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.\n","operationId":"generateInvoiceNow","deprecated":true,"x-deprecation":"nif-migration","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/generate"},"x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"201":{"description":"Invoice generated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"invoice_id":{"type":"string","format":"uuid"},"next_generation":{"type":"string","format":"date","nullable":true,"description":"The template's next generation date **after** this call consumed the\npending occurrence. `null` when the advance took the template past\nits `end_date` and its status is now `COMPLETED`: there is no next\ngeneration left.\n"}}}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"The template cannot generate right now, or the company/NIF is not ready to issue in\nthis environment (`error.code` `EMISSION_NOT_READY`). When the template's\n`start_date` is still in the future, `error.code` is `RECURRING_NOT_STARTED`: there\nis no occurrence to bring forward yet, nothing is created and `next_generation` does\nnot move. To bill before that date, create a normal invoice.\nThe fiscal rules on the lines are checked again here and answer `422` with their own\n`error.code`: `SIMPLIFICADA_FORBIDS_ISP`, `SIMPLIFICADA_FORBIDS_SURCHARGE` and\n`SIMPLIFICADA_FORBIDS_CROSS_BORDER` (a `SIMPLIFIED` invoice cannot carry reverse charge,\nan equivalence surcharge or an operation of art. 4.4 RD 1619/2012),\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` (the recipient lacks the identifier type the\n`exemption_reason` needs, for example after the customer lost its `NIF_IVA`),\n`EXEMPTION_NOT_FOR_ISSUED_INVOICE` (an exemption no longer accepted on a new invoice),\n`REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION` (a `regime_key` that requires an exemption the\nline does not carry) and `VAT_RATE_NOT_ACCEPTED_ON_DATE` (a VAT rate AEAT does not accept\non the operation date, whose message names the dates that rate is accepted).\n\nOther `error.code` values with this status:\n\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/skip":{"post":{"tags":["RecurringInvoices"],"summary":"Skip the next generation","description":"**Deprecated.** Use `POST /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/skip`, which behaves identically.\n\nSkips the next scheduled generation and advances the generation date to the following\nperiod. Nothing is issued.\n\n**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.\n","operationId":"skipNextGeneration","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/skip"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Next generation skipped successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/preview":{"get":{"tags":["RecurringInvoices"],"summary":"Preview the next invoice of a recurring template","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/next-occurrence`, which behaves identically.\n\nReturns the invoice that would be produced by the next generation of this recurring\ntemplate, computed from the current issuer, recipient and series data. Nothing is\npersisted.\n\n**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.\n","operationId":"previewRecurringInvoice","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/next-occurrence"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"UUID of the recurring invoice template","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Invoice preview computed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/NextOccurrence"}}}]}}}},"400":{"description":"Branch on `error.code`:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"The template cannot produce an invoice as it stands — for example an `ORDINARIA`\ntemplate with no customer and no recipient tax id, or `VAT_RATE_NOT_ACCEPTED_ON_DATE`\nwhen a line carries a VAT rate AEAT no longer accepts on the date of the next invoice\n(a temporary rate such as 5 % whose window has closed). The next generation would\nfail for the same reason, so no preview is returned.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/recurring-invoices/{recurring_invoice_id}/history":{"get":{"tags":["RecurringInvoices"],"summary":"Get the generation history of a recurring invoice","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/history`.\n\nReturns the invoices previously generated from this recurring template, including their\nstatus and generation dates. **Only generated invoices**: this route never reports failed\nruns, skipped slots or pauses — the successor does.\n\n- **Response shape:** it differs from the successor's, and that is why this notice exists.\n  This route is frozen as it shipped until its `Sunset` date: it returns the **whole**\n  history in `data.history` and carries no `pagination`. The successor pages with\n  `page`/`limit` and answers the first 20 entries by default. Send `limit` and read\n  `data.pagination` when you migrate.\n\n**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.\n","operationId":"getRecurringHistory","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/history"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Generation history retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["history"],"properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/GenerationHistoryResponse"}}}}}}]}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoices/{invoice_id}/create-recurring":{"post":{"tags":["RecurringInvoices"],"summary":"Create a recurring invoice from an existing invoice","description":"**Deprecated.** Use `POST /v1/companies/{company_id}/recurring-invoices/derivations`, which creates the\nsame template. The source invoice travels in the body as `from_invoice_id`, not in the path.\n\nCreates a recurring invoice template taking its lines, recipient and configuration from an\nexisting invoice.\n\n**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.\n","operationId":"createRecurringFromInvoice","deprecated":true,"x-deprecation":"nif-migration","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/recurring-invoices/derivations"},"x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"invoice_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecurringFromInvoiceRequest"}}}},"responses":{"201":{"description":"Recurring invoice created from existing invoice","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"Branch on `error.code`:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"Validation error.\nAlso: `frequency` and `end_date` can each be valid on their own and still be\nimpossible together, when the window between `start_date` and `end_date` holds no\noccurrence at all — a `YEARLY` template covering a calendar year, created with the year\nalready under way, is the everyday case. The cadence and the end date come from this\nrequest, not from the source invoice, so a derivation can ask for that window just like\na template created from scratch. It is rejected with `error.code`\n`RECURRING_NO_OCCURRENCE_IN_WINDOW`, and its `error.details` hangs off `end_date`,\nthe field you can move; the other lever is `frequency`. It is not the same as\n`RECURRING_END_DATE_BEFORE_START`, which says the *next* generation of a template that\nalready had one falls past its end: this one says there is no first invoice, and never\nwill be. Retrying does not help — widen `end_date` or shorten `frequency`.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `PROFORMA_NOT_RECURRING` — the source invoice is a proforma; a recurring invoice can only be created from a standard or simplified invoice.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECURRING_TYPE_NOT_SUPPORTED` — the source invoice is a corrective invoice, which cannot seed a recurrence. Derive it from a standard or simplified invoice.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyRecurringInvoices","tags":["CompanyRecurringInvoices"],"summary":"List recurring invoices","description":"Lists the recurring invoice templates of this company, with filters and pagination.\nOnly the templates of the company in the path are returned.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Keeps only the schedules in this lifecycle state. Omitted, every state is listed.","schema":{"$ref":"#/components/schemas/RecurringInvoiceStatus"}},{"name":"customer_id","in":"query","required":false,"description":"Keeps only the schedules that invoice this customer of the company.","schema":{"type":"string","format":"uuid"}},{"name":"pause_reason","in":"query","required":false,"description":"Keeps only the schedules stopped for this reason — the same value the response returns\nin `pause.reason`. A single value; repeating the parameter is rejected.\n\nIt combines with `status` as an AND, with no special case: `status=ACTIVE` together with\nany `pause_reason` answers `200` with an empty list, because an active schedule has no\npause to have a reason. Sending it alone already implies `PAUSED`, since that is the\nonly state that records one.\n","schema":{"$ref":"#/components/schemas/RecurringInvoicePauseReason"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"sort_by","in":"query","required":false,"description":"Field to sort by. Defaults to `created_at` when omitted.","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortBy"}},{"name":"sort_order","in":"query","required":false,"description":"Sort direction. Defaults to `desc` when omitted.","schema":{"$ref":"#/components/schemas/RecurringInvoiceSortOrder"}}],"responses":{"200":{"description":"Recurring invoices list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["recurring_invoices"],"properties":{"recurring_invoices":{"type":"array","items":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"createCompanyRecurringInvoice","tags":["CompanyRecurringInvoices"],"summary":"Create a recurring invoice","description":"Creates a recurring invoice template under this company: the invoice data it repeats\n(lines, recipient, series, payment) plus the recurrence that drives it.\n\n- **Cadence:** `frequency` is how often it generates — every 1 (`MONTHLY`), 3\n  (`QUARTERLY`) or 12 (`YEARLY`) months — on `day_of_month`, from `start_date` until\n  `end_date` if one is given. Omitted, `MONTHLY` applies.\n- **The cadence governs the step, not the first invoice:** the first occurrence is the\n  first `day_of_month` on or after `start_date`, found one month at a time whatever the\n  cadence; the cadence takes over from there. A `YEARLY` template starting 15 February\n  with `day_of_month` 10 first invoices on 10 March, then every 10 March after that — it\n  does not wait a year.\n- **`start_date` in the past:** accepted and stored as sent, but it never anchors\n  generation backwards. `next_generation` becomes the first date of the template's own\n  calendar on or after today (today included) — the grid of `day_of_month` dates\n  anchored at `start_date`, one every `frequency` — so on a quarterly or yearly\n  template it can land months from now, not this month. The missed periods are not\n  generated. If today's period was already invoiced in the system you are migrating\n  from, send a `start_date` of tomorrow (or of the next period) so it is not invoiced\n  twice.\n- **`draft_in_advance`:** whether the invoice is created as a draft for review before it\n  is emitted. The window is fixed at 5 days, and both options emit on the scheduled day.\n  Generating manually a period whose scheduled date is today or earlier emits it on the\n  spot. Omitted, no review draft is prepared. It supersedes the deprecated `preview_days`.\n- **VeriFactu:** the template does not carry it. Whether each generated invoice is\n  registered with AEAT is decided when that invoice is issued, against the company's\n  regime at that moment.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecurringInvoiceRequest"},"examples":{"create_monthly_recurring_invoice":{"$ref":"#/components/examples/create-monthly"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Recurring invoice created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The `customer_id` referenced in the body does not exist. It never refers to the NIF in\nthe path: a NIF you cannot reach answers `403`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error.\nA line whose `exemption_reason` requires the recipient to carry a specific alternate\nidentifier type (today, `EXENTA_ART_25` needs `NIF_IVA`) is rejected with `error.code`\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` when the customer referenced by `customer_id`\ndoes not carry it. No template is created — the same rejection\n`POST /v1/companies/{company_id}/invoices` gives for the same case, brought forward to\nthe write instead of every generated invoice bouncing at the AEAT.\nAlso, from the template's payment and delivery settings: `payment_method` `BANK_TRANSFER`\nor `DIRECT_DEBIT` without `payment_iban` answers `IBAN_REQUIRED`; a `payment_term_days`\noutside 0–365 answers `PLAZO_DIAS_OUT_OF_RANGE`; and `send_automatically: true` with\nnobody to send to — no recipients in `email_configuration`, no `billing_emails` on the\ncustomer and no recipient email — answers `SIN_DESTINATARIO_RESOLUBLE`, since every\ngenerated invoice would fail to send.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAlso: `frequency` and `end_date` can each be valid on their own and still be\nimpossible together, when the window between `start_date` and `end_date` holds no\noccurrence at all — a `YEARLY` template covering a calendar year, created with the year\nalready under way, is the everyday case. That is rejected with `error.code`\n`RECURRING_NO_OCCURRENCE_IN_WINDOW`, and its `error.details` hangs off `end_date`,\nthe field you can move; the other lever is `frequency`. It is not the same as\n`RECURRING_END_DATE_BEFORE_START`, which says the *next* generation of a template that\nalready had one falls past its end: this one says there is no first invoice, and never\nwill be. Retrying does not help — widen `end_date` or shorten `frequency`.\nAlso: `series_id` (explicit or resolved by default) numbering against a series that is\ninactive is rejected with `error.code` `SERIES_INACTIVE` — no template is created.\nActivate the series, or send a `series_id` for one that is already active.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `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`.\n- `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.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.\n- `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`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/stats":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyRecurringInvoiceStats","tags":["CompanyRecurringInvoices"],"summary":"Get recurring invoice stats","description":"How many recurring schedules this company has alive, what they add up to per month, and\nhow much invoicing is stopped because the unattended generation broke.\n\n**It is the whole company, and it takes no filters.** It is an anchor, not a summary of\nwhatever the list is showing: narrowing the list by customer or by status does not move\nthese figures. Pagination does not apply either — the numbers cover every schedule of the\ncompany, not a page of them.\n\n**The two amounts are never added together.** `active.monthly_amount` is a forecast of\nwhat is going to be invoiced; `stopped.monthly_amount` is invoicing that should be\nhappening and is not. There is deliberately no grand total in the response.\n\nThe per-schedule figures behind them are the same ones\n`GET /v1/companies/{company_id}/recurring-invoices` publishes as `amount`, so the rows\nand this header cannot drift: adding the `amount` of every active row by hand gives\n`active.monthly_amount` exactly, to the cent.\n\nFilters of the list are **rejected**, not ignored: sending `status`, `customer_id` or any\nother unknown parameter answers `400` naming it. Asking for a filtered header and getting\nwhole-company figures back with a `200` would be worse than being told no.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Recurring invoice stats retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceStats"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyRecurringInvoice","tags":["CompanyRecurringInvoices"],"summary":"Get a recurring invoice","description":"Retrieves the full details of a recurring invoice template of this company, including its schedule, template lines and next generation date.","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Recurring invoice retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyRecurringInvoice","tags":["CompanyRecurringInvoices"],"summary":"Update a recurring invoice","description":"Updates only the fields present in the body, leaving every other field of the recurring\ninvoice template as it is.\n\n- **Omitted vs `null`:** an omitted field keeps its current value; a field sent as `null`\n  is cleared, and only where the request schema documents the field as nullable.\n- **`lines`:** replaced as a whole, not patched line by line. The recipient survives the\n  change, and an empty array is rejected.\n- **`payment_method`:** replaced as a whole together with `payment_iban`, `payment_swift`\n  and `payment_term_days` — send them in the same request or they are dropped.\n- **Schedule:** `frequency`, `day_of_month` and `start_date` stay put unless you send\n  them; sending a new value for any of the three moves the next generation — resending\n  the ones already in effect changes nothing. Changing `frequency` recalculates it on\n  the new grid and discards a pending skip. `start_date` is only editable while the\n  template has not generated any invoice yet.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchRecurringInvoiceRequest"},"examples":{"patch_recurring_invoice_schedule":{"$ref":"#/components/examples/patch-schedule"},"patch_recurring_invoice_lines":{"$ref":"#/components/examples/patch-lines"}}}}},"responses":{"200":{"description":"Recurring invoice updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n\nOther `error.code` values with this status:\n\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error.\nThe update is judged on the resulting template, not only on the fields the request\ntouches: if a line ends up with an `exemption_reason` that requires the recipient to\ncarry a specific alternate identifier type (today, `EXENTA_ART_25` needs `NIF_IVA`) —\nwhether `customer_id` changed in this same request or the line did — and the customer\ndoes not carry it, the update is rejected with `error.code`\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` and nothing is written; the template keeps the\ncustomer and lines it had.\nAlso, from the template's payment and delivery settings: `payment_method` `BANK_TRANSFER`\nor `DIRECT_DEBIT` without `payment_iban` answers `IBAN_REQUIRED`; a `payment_term_days`\noutside 0–365 answers `PLAZO_DIAS_OUT_OF_RANGE`; and `send_automatically: true` with\nnobody to send to — no recipients in `email_configuration`, no `billing_emails` on the\ncustomer and no recipient email — answers `SIN_DESTINATARIO_RESOLUBLE`, since every\ngenerated invoice would fail to send.\nSeparately: on a line priced by declared total (`total_excluding_tax` or\n`total_including_tax`) the unit price is not sent — it is derived as total ÷ quantity —\nso a quotient that does not fit the field storing it is rejected with `error.code`\n`LINE_UNIT_PRICE_OUT_OF_RANGE` and an `error.details` that follows\n`LineUnitPriceOutOfRangeDetails`: `field` names the offending line (`lines[0]`), not a\n`unit_price` you never sent, while `max` and `derived_unit_price` carry the limit and\nthe quotient as JSON numbers, so they can be compared without parsing `error.message`.\nA `unit_price` you send with more than 4 decimal places is rejected with `error.code`\n`LINE_UNIT_PRICE_TOO_MANY_DECIMALS` instead of being rounded.\nAlso: changing `frequency` is checked against the window. If the new cadence leaves\nno occurrence at all between `start_date` and `end_date`, the update is rejected with\n`error.code` `RECURRING_NO_OCCURRENCE_IN_WINDOW`, whose `error.details` hangs off\n`end_date` — the field you can move; the other lever is `frequency` itself. Nothing is\nwritten, so the template keeps the cadence and the `next_generation` it had. Shortening\n`end_date` without touching `frequency` is a different case and not this check: that\nstill answers `RECURRING_END_DATE_BEFORE_START` when the end falls before the next\ngeneration — also on a `PAUSED` template, and only when the request actually changes\n`end_date` (an omitted or resent-unchanged value never triggers it). The date it is\njudged against is not necessarily the one on file: it is whatever `next_generation`\nresuming would leave — the stored one if it had not gone stale yet, one recalculated\nfrom today if it had. A `PAUSED` template whose window already elapsed on its own,\nwith `end_date` left untouched, keeps accepting every other field; that one still\ncloses as `COMPLETED` only when actually resumed.\nAlso: the update is judged on the resulting template, not only on the fields the\nrequest touches. If the template's series ends up inactive — its current one, left\nuntouched, or one sent in this same request — the update is rejected with `error.code`\n`SERIES_INACTIVE` and nothing is written. Activate the series, or send a `series_id`\nfor one that is already active.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `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.\n- `LINE_EXEMPTION_TEXT_REQUIRED` — `exemption_reason: OTRO` needs `exemption_reason_text` explaining the exemption; add it.\n- `LINE_EXEMPTION_WITH_TAX` — the line declares an `exemption_reason` but a non-zero rate; exempt or non-subject lines go at 0 %.\n- `LINE_NORMAL_DESCRIPTION_REQUIRED` — a `NORMAL` line has an empty or blank `description`; describe the invoiced concept.\n- `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.\n- `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`.\n- `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.\n- `PERCENTAGE_OUT_OF_RANGE` — a percentage (discount, tax, surcharge or withholding) is below 0 or above 100.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `RECURRING_MAX_INVOICES_ABOVE_MAXIMUM` — `max_invoices` is above 600. Send a lower cap, or `null` for a recurrence not capped by number.\n- `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.\n- `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`.\n- `RECURRING_START_DATE_NOT_EDITABLE` — the template already generated invoices, so `start_date` cannot change. Resend the current value or leave it out.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyRecurringInvoice","tags":["CompanyRecurringInvoices"],"summary":"Delete a recurring invoice","description":"Permanently deletes a recurring invoice template of this company and cancels any pending scheduled generations. Invoices already generated from it are not affected.","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"responses":{"204":{"description":"Recurring invoice deleted successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"put":{"operationId":"setCompanyRecurringInvoiceStatus","tags":["CompanyRecurringInvoices"],"summary":"Set the status of a recurring invoice","description":"Sets the lifecycle status of a recurring invoice template. This is how generation is\npaused, resumed and ended.\n\n- **`PAUSED`:** stops automatic generation, keeping the schedule configuration intact.\n- **`ACTIVE`:** resumes generation. It keeps the scheduled next generation date whenever\n  that date has not fallen due yet (including today), so resuming never re-issues a period\n  you already invoiced and never undoes a skipped one. Only a date left in the past is\n  rescheduled, to the first occurrence after today; the periods missed while the template\n  was paused are not backfilled. If nothing is left to generate — the next generation date\n  falls beyond `end_date`, or `max_invoices` has already been reached — the template\n  becomes `COMPLETED`; for `end_date` that holds whether the date was kept or rescheduled.\n- **`COMPLETED`:** ends the schedule for good, from `ACTIVE` or `PAUSED`; it is also\n  reached on its own when the schedule runs out. It is terminal: `ACTIVE` and `PAUSED`\n  are then rejected with `RECURRING_STATE_TRANSITION_INVALID`.\n- **Rejected transitions:** resuming a template that is already active, or one whose\n  `pause.blocker` is still in effect.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetRecurringInvoiceStatusRequest"},"examples":{"set_recurring_invoice_paused":{"$ref":"#/components/examples/status-paused"},"set_recurring_invoice_active":{"$ref":"#/components/examples/status-active"},"set_recurring_invoice_completed":{"$ref":"#/components/examples/status-completed"}}}}},"responses":{"200":{"description":"Status applied successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECURRING_STATE_TRANSITION_INVALID` — the template cannot move from its current state to the requested one (e.g. a `COMPLETED` template never changes again). `error.details` carries `current_status` and `target_status`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/skip":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"skipCompanyRecurringInvoice","tags":["CompanyRecurringInvoices"],"summary":"Skip the next generation of a recurring invoice","description":"Skips the next scheduled invoice generation and advances the generation date to the following period. Nothing is issued.","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Next generation skipped successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/next-occurrence":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyRecurringInvoiceNextOccurrence","tags":["CompanyRecurringInvoices"],"summary":"Get the next occurrence of a recurring invoice","description":"Returns the invoice that would be produced by the next generation of this recurring\ntemplate, computed from the current issuer, recipient and series data. Nothing is\npersisted and no numbering is consumed.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"responses":{"200":{"description":"Next occurrence computed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/NextOccurrence"}}}]}}}},"400":{"description":"The request target could not be read as declared, so nothing was looked up. Three causes,\nall named in `error.details`: a path or query parameter whose value does not parse as its\ntype (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty\nelement in a list such as `status=ISSUED,`), a required parameter that was not sent\n(`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not\n(`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is\nnot an error: it is the same as omitting it.\n\nThe fault is in the target, not in the content — which is what `422` is defined over.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The parameter 'invoice_id' has an invalid type. Expected: UUID.","details":{"field":"invoice_id","invalid_value":"deliveries","expected_format":"UUID"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"The template cannot produce an invoice as it stands — for example an `ORDINARIA`\ntemplate with no customer and no recipient tax id, or `VAT_RATE_NOT_ACCEPTED_ON_DATE`\nwhen a line carries a VAT rate AEAT no longer accepts on the date of the next invoice\n(a temporary rate such as 5 % whose window has closed). The next generation would\nfail for the same reason, so no preview is returned.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/history":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyRecurringInvoiceHistory","tags":["CompanyRecurringInvoices"],"summary":"Get the generation history of a recurring invoice","description":"Returns **every entry** in this schedule's history, newest first: the invoices it generated\nand, just as important, the periods it did **not** — a failed unattended run, a slot a user\nskipped, or the schedule being paused. Each entry carries its `type`, the `origin` of who\nasked for it, a translated `reason` when there is one, and `requested_by` for a skip. That is\nwhat lets you answer \"why is there no invoice for March?\" without reading logs.\n\nOnly `GENERATED` entries have an `invoice_id`; for the rest it is `null`.\n\n**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get\nthe 20 most recent entries, not the whole history — which grows with every cycle the\ntemplate runs. Read `data.pagination` to walk the rest.\n\nThe deprecated flat alias `GET /v1/recurring-invoices/{recurring_invoice_id}/history` does\n**not** paginate and returns **only generated invoices**: it is frozen as it shipped until\nits `Sunset` date. Only this route returns every entry type.\n","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"Generation history retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["history","pagination"],"properties":{"history":{"type":"array","items":{"$ref":"#/components/schemas/GenerationHistoryResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/derivations":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyRecurringInvoiceDerivation","tags":["CompanyRecurringInvoices"],"summary":"Derive a recurring invoice from an existing invoice","description":"Creates a recurring invoice template of this company taking its lines, recipient, series\nand payment data from an existing invoice, so only the recurrence has to be described.\n\n- **`from_invoice_id`:** the source invoice. It must belong to the company in the path,\n  and one you cannot reach is reported the same way as one that does not exist. It is\n  not modified by this call.\n- **Recurrence:** `name`, `day_of_month` and `start_date` are required; `end_date` and\n  `frequency` are optional. The cadence is not taken from the source invoice — a one-off\n  invoice has none to copy — so it is described here like any other recurrence field:\n  every 1 (`MONTHLY`), 3 (`QUARTERLY`) or 12 (`YEARLY`) months, `MONTHLY` when omitted.\n  It governs the step from the first invoice onwards, not where that first one lands.\n- **VeriFactu:** not inherited from the source invoice. Each generated invoice is\n  registered with AEAT, or not, according to the company's regime when it is issued.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecurringInvoiceDerivationRequest"},"examples":{"create_recurring_invoice_derivation":{"$ref":"#/components/examples/derivation-from-invoice"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Recurring invoice created successfully from the source invoice","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecurringInvoiceResponse"}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The `from_invoice_id` does not exist under this company. An invoice belonging to\nanother NIF answers the same way.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error.\nAlso: `frequency` and `end_date` can each be valid on their own and still be\nimpossible together, when the window between `start_date` and `end_date` holds no\noccurrence at all — a `YEARLY` template covering a calendar year, created with the year\nalready under way, is the everyday case. The cadence and the end date come from this\nrequest, not from the source invoice, so a derivation can ask for that window just like\na template created from scratch. It is rejected with `error.code`\n`RECURRING_NO_OCCURRENCE_IN_WINDOW`, and its `error.details` hangs off `end_date`,\nthe field you can move; the other lever is `frequency`. It is not the same as\n`RECURRING_END_DATE_BEFORE_START`, which says the *next* generation of a template that\nalready had one falls past its end: this one says there is no first invoice, and never\nwill be. Retrying does not help — widen `end_date` or shorten `frequency`.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `PROFORMA_NOT_RECURRING` — the source invoice is a proforma; a recurring invoice can only be created from a standard or simplified invoice.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `RECURRING_TYPE_NOT_SUPPORTED` — the source invoice is a corrective invoice, which cannot seed a recurrence. Derive it from a standard or simplified invoice.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_ISP` — a simplified invoice cannot carry a reverse-charge (`ISP_*`) line; issue a standard invoice.\n- `SIMPLIFICADA_FORBIDS_SURCHARGE` — a simplified invoice cannot carry an equivalence surcharge; send `equivalence_surcharge_rate: 0` or issue a standard invoice.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/generate":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"recurring_invoice_id","in":"path","required":true,"description":"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.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"generateCompanyRecurringInvoiceNow","tags":["CompanyRecurringInvoices"],"summary":"Generate an invoice now from a recurring template","description":"Runs the generation of this recurring template immediately, out of its schedule. It is a\nfiscal act: the generated invoice consumes numbering from the series of the template and,\nwhen the template says so, is issued and sent.\n\n- **It brings the upcoming occurrence forward, it does not add one:** the call consumes\n  the period that was pending, so the invoice is created now and `next_generation`\n  advances one period. Generating manually, skipping and letting the schedule run each\n  consume exactly one occurrence, so a monthly template still produces twelve invoices a\n  year however you mix the three.\n- **`next_generation` in the response:** the template's next date after this call\n  consumed the pending occurrence, or `null` when the advance took the template past its\n  `end_date` and its status is now `COMPLETED`.\n- **Templates with `draft_in_advance`:** when the pending period's scheduled date is\n  today or earlier, its review window has passed and the invoice is issued on the spot,\n  dated today. When it is still ahead, the call creates the review draft with the\n  scheduled date, and it is issued on that day.\n- **An extra invoice outside the calendar:** do not use this endpoint. Create a normal\n  invoice, or derive a draft from one the template already generated with\n  `POST /v1/companies/{company_id}/invoices/derivations`. Either way the schedule stays\n  where it was.\n","x-required-scopes":["invoices:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Invoice generated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"invoice_id":{"type":"string","format":"uuid"},"next_generation":{"type":"string","format":"date","nullable":true,"description":"The template's next generation date **after** this call consumed the\npending occurrence. `null` when the advance took the template past\nits `end_date` and its status is now `COMPLETED`: there is no next\ngeneration left.\n"}}}}}]}}}},"400":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU` — `REGIMEN_ART_*` reasons are special regimes, not exemptions; declare the matching `regime_key` instead of an exemption reason.\n- `ISP_INCOMPATIBLE_WITH_SURCHARGE` — a reverse-charge line (`ISP_*`) cannot carry an equivalence surcharge; remove the surcharge or the reverse-charge reason.\n- `LINE_TOTAL_INCONSISTENT` — the computed line amounts do not add up to its total within 1 cent; this is an internal safeguard, report it with the request id.\n- `OSS_REGIME_INCOMPATIBLE_WITH_ISP` — regime key `17` (OSS/IOSS) cannot be combined with a reverse-charge `exemption_reason`; choose one.\n- `RECURRING_NOT_ACTIVE` — the template is not `ACTIVE`, so it cannot generate, skip or preview. `error.details.current_status` says its state; resume it first if it is `PAUSED`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"The template cannot generate right now, or the company/NIF is not ready to issue in\nthis environment (`error.code` `EMISSION_NOT_READY`). When the template's\n`start_date` is still in the future, `error.code` is `RECURRING_NOT_STARTED`: there\nis no occurrence to bring forward yet, nothing is created and `next_generation` does\nnot move. To bill before that date, create a normal invoice.\nThe fiscal rules on the lines are checked again here and answer `422` with their own\n`error.code`: `SIMPLIFICADA_FORBIDS_ISP`, `SIMPLIFICADA_FORBIDS_SURCHARGE` and\n`SIMPLIFICADA_FORBIDS_CROSS_BORDER` (a `SIMPLIFIED` invoice cannot carry reverse charge,\nan equivalence surcharge or an operation of art. 4.4 RD 1619/2012),\n`EXEMPTION_REQUIRES_RECIPIENT_ID_TYPE` (the recipient lacks the identifier type the\n`exemption_reason` needs, for example after the customer lost its `NIF_IVA`),\n`EXEMPTION_NOT_FOR_ISSUED_INVOICE` (an exemption no longer accepted on a new invoice),\n`REGIME_REQUIRES_INCOMPATIBLE_EXEMPTION` (a `regime_key` that requires an exemption the\nline does not carry) and `VAT_RATE_NOT_ACCEPTED_ON_DATE` (a VAT rate AEAT does not accept\non the operation date, whose message names the dates that rate is accepted).\n\nOther `error.code` values with this status:\n\n- `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.\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `RECIPIENT_ID_REQUIRED` — a standard invoice needs an identified recipient: a `customer_id`, or inline data with `nif` or `alternative_id`.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n- `REGIME_KEY_REQUIRES_RECIPIENT_NIF` — regime key `10` (collections on behalf of third parties) requires a recipient identified with a Spanish NIF.\n- `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.\n- `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.\n- `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.\n- `SIMPLIFICADA_FORBIDS_IRPF` — a simplified invoice cannot carry IRPF withholding; remove `irpf_rate` or issue a standard invoice.\n- `SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE` — AEAT does not accept that surcharge rate on the operation date (or issue date if none); send the right `operation_date` or rate.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers":{"get":{"tags":["Customers"],"summary":"List the customers","description":"Returns a paginated list of customers, with optional filters.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/customers`, which behaves identically.\n\n**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.\n","operationId":"listCustomers","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/customers"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"active","in":"query","description":"Filter by active/inactive status. Defaults to `true`, so inactive customers must be requested\nexplicitly with `active=false`. Deleted customers are never returned by either value.\n","schema":{"type":"boolean"}},{"name":"search","in":"query","description":"Global search by name, NIF or email","schema":{"type":"string"}},{"name":"legal_name","in":"query","description":"Filter by legal name (partial search case-insensitive)","schema":{"type":"string"}},{"name":"nif","in":"query","description":"Filter by NIF (partial search)","schema":{"type":"string"}},{"name":"email","in":"query","description":"Filter by email (partial search)","schema":{"type":"string"}},{"name":"phone","in":"query","description":"Filter by phone (partial search)","schema":{"type":"string"}},{"name":"city","in":"query","description":"Filter by city","schema":{"type":"string"}},{"name":"province","in":"query","description":"Filter by province","schema":{"type":"string"}},{"name":"sort_by","in":"query","description":"Field to sort by. Results are always tie-broken by a stable internal key, so paging through the collection never repeats or skips a customer.","schema":{"allOf":[{"$ref":"#/components/schemas/CustomerSortBy"}],"default":"legal_name"}},{"name":"sort_order","in":"query","description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"asc"}}],"responses":{"200":{"description":"Customer list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["customers","pagination"],"properties":{"customers":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"examples":{"list_success":{"$ref":"#/components/examples/list-customers-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["Customers"],"summary":"Create a customer","description":"Creates a new customer.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/customers`, which behaves\n  identically.\n\n**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.\n","operationId":"createCustomer","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/customers"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerRequest"},"examples":{"cliente_empresa":{"$ref":"#/components/examples/company-customer"},"cliente_autonomo":{"$ref":"#/components/examples/freelancer-customer"},"cliente_extranjero_ue":{"$ref":"#/components/examples/eu_customer"},"cliente_extranjero_usa":{"$ref":"#/components/examples/us_customer"},"cliente_spanish_business":{"$ref":"#/components/examples/spanish-business"}}}}},"responses":{"201":{"description":"Customer created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]},"examples":{"created_success":{"$ref":"#/components/examples/created-success-2"}}}}},"400":{"$ref":"#/components/responses/InvalidJsonFormat"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"`CLIENT_DUPLICATE` — a customer of this company already carries that NIF or alternative identifier. `details` is an empty object; the identifier is not echoed back. The body is parsed and validated first, so a `400` or `422` always takes precedence over this conflict; a JSON number sent for a string field is coerced, not rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"conflict":{"$ref":"#/components/examples/conflict-409"}}}}},"422":{"description":"The body parses but is not acceptable. `VALIDATION_ERROR` when a property breaks a\nconstraint (`details` maps each offending property to a message). A Spanish NIF is also\nchecked against the AEAT census: `NIF_NOT_IN_CENSUS` when the NIF does not exist there or,\nfor a natural person, `legal_name` does not match the name the census holds (no `details`).\nFor a company the census identifies the holder by its CIF alone, so the name is not verified.\nA NIF that is malformed answers `NIF_INVALID_FORMAT` or `NIF_INVALID_CONTROL_DIGIT`.\n\nOther `error.code` values with this status:\n\n- `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` — the census identifies that Spanish `NOT_REGISTERED` (07) DNI or NIE with the given `legal_name`; send it in `nif` instead of `alternative_id`.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers/{customer_id}":{"get":{"tags":["Customers"],"summary":"Get a customer","description":"Retrieves the complete details of a customer.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/customers/{customer_id}`, which\n  behaves identically.\n\n**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.\n","operationId":"getCustomer","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/customers/{customer_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"parameters":[{"name":"customer_id","in":"path","required":true,"description":"Customer ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Customer retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]},"examples":{"get_success":{"$ref":"#/components/examples/get-customer-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["Customers"],"summary":"Replace a customer","description":"Replaces an existing customer with the body you send.\n\n- **Not a partial update:** `trade_name`, `email`, `website`, `billing_emails`,\n  `contact_person`, `notes` and `general_discount` are cleared when they are absent from the\n  body, so send the customer complete. To change only some fields, use\n  `PATCH /v1/companies/{company_id}/customers/{customer_id}`.\n- **AEAT census:** the customer's Spanish tax identifier is only checked against the AEAT\n  census when the body changes the `nif` or the `legal_name` of the stored customer.\n  Resending the same pair never asks the census, so a customer stored long ago stays\n  editable even if its NIF is no longer listed.\n  For a **legal entity** the census checks the CIF only and its `legal_name` is not\n  verified, so the name never causes the rejection; only a **natural person**'s NIF is\n  crossed with the name.\n  Duplicate and format checks run on every update.\n- **Deprecated:** this route will be retired on the date announced in its `Sunset` response\n  header. The canonical form has a single update verb,\n  `PATCH /v1/companies/{company_id}/customers/{customer_id}`, which is not a drop-in\n  replacement for this one: it changes only the fields present in the body. To reproduce a\n  total replacement, send every field and pass `null` in the ones you want cleared.\n\n**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.\n","operationId":"updateCustomer","deprecated":true,"x-deprecation":"nif-migration","x-no-successor":"The canonical form only has PATCH, which leaves absent fields untouched instead of clearing them: it is not a drop-in destination for this route. Migration guidance is in the description — send every field and pass null in the ones you want cleared.","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"name":"customer_id","in":"path","required":true,"description":"Customer ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCustomerRequest"}}}},"responses":{"200":{"description":"Customer updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidJsonFormat"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["Customers"],"summary":"Update a customer partially","description":"Updates only the fields present in the body, leaving every other field of the customer as it\nis.\n\n- **Null vs omitted:** a field sent as `null` is cleared, which is different from omitting\n  it (see `PatchCustomerRequest`). The result goes through the same validation as `PUT`.\n- **AEAT census:** the customer's Spanish tax identifier is only checked against the AEAT\n  census when the request changes the `nif` or the `legal_name`. Editing anything else never\n  asks the census.\n  For a **legal entity** the census checks the CIF only and its `legal_name` is not\n  verified, so the name never causes the rejection; only a **natural person**'s NIF is\n  crossed with the name.\n  Duplicate and format checks run on every update.\n- **Deprecated:** use `PATCH /v1/companies/{company_id}/customers/{customer_id}`, which\n  behaves identically.\n\n**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.\n","operationId":"patchCustomer","x-successor":{"method":"PATCH","path":"/v1/companies/{company_id}/customers/{customer_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"name":"customer_id","in":"path","required":true,"description":"Customer ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchCustomerRequest"}}}},"responses":{"200":{"description":"Customer updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidJsonFormat"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate customer (same tax identifier)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Customers"],"summary":"Delete a customer","description":"Deletes a customer that has no invoices.\n\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/customers/{customer_id}`, which\n  deletes the same way but answers `204` with no body instead of `200`. This route keeps\n  working until the date announced in its `Sunset` response header.\n\n## What deleting means\n\n- **No longer exposed:** the customer is retained internally for tax record-keeping\n  purposes, but is no longer exposed by the API: subsequent requests to it return `404`, and\n  it is never included in the customer list, under any value of the `active` filter.\n- **Identifier released:** its NIF or alternative identifier is freed, so a new customer may\n  be created with the same identifier.\n\n## Customers you cannot delete\n\n- **Customers with invoices:** they cannot be deleted and the request answers `409`\n  `CLIENT_HAS_INVOICES`, leaving the customer untouched — neither deleted nor deactivated.\n- **Customers used by a recurring invoice:** while an active or paused recurring invoice\n  has the customer as its recipient, the request answers `400`\n  `REFERENCED_BY_RECURRING_INVOICE`.\n- **Deactivating instead:** to stop using a customer, whether or not it has invoices, update\n  it with `active` set to `false`: that releases no identifier and keeps the customer\n  retrievable through `GET /v1/customers?active=false`.\n\n**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.\n","operationId":"deleteCustomer","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/customers/{customer_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"name":"customer_id","in":"path","required":true,"description":"Customer ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Customer deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["message"],"properties":{"message":{"type":"string","example":"Customer deleted successfully"}}}}}]}}}},"400":{"description":"An active or paused recurring invoice uses this customer as its recipient\n(`REFERENCED_BY_RECURRING_INVOICE`). The customer is left untouched. End that recurring\ninvoice or change its customer first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"The customer has invoices (`CLIENT_HAS_INVOICES`). The customer is left untouched — it is\nneither deleted nor deactivated. Update it with `active` set to `false` to stop using it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers/bulk":{"post":{"tags":["Customer Import"],"summary":"Create several customers","description":"Creates up to 500 customers in a single call.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/customers/bulk`, which validates\n  and creates the same way. A dry run still answers `200`; an actual creation answers\n  `201` instead of `200`.\n- **Atomic:** if any customer fails validation the whole batch is rejected with\n  `422 BULK_VALIDATION_ERROR` and nothing is persisted.\n- **`dry_run`:** with `true` the batch is only validated — tax identifiers against the\n  AEAT register, duplicates inside the batch and against the existing customers, field\n  formats — and nothing is written. With `false`, the default, validation is followed by\n  creation.\n- **Report:** both modes return the same per-record report, so a dry run and a real run\n  are read the same way.\n\n**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.\n","operationId":"createCustomersBulk","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/customers/bulk"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"dry_run","in":"query","description":"Validate the batch without persisting it (`true`), or validate and create it (`false`,\nthe default). Either way the batch is atomic.\n","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customers"],"properties":{"customers":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/CreateCustomerRequest"}}}}}}},"responses":{"200":{"description":"Validation completed successfully (dry_run=true).\nReturns validation results without persisting data using unified format.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"201":{"description":"Customers created (`dry_run=false`). Atomic operation: either every customer of the\nbatch was created, or none was. The body is the same unified report as the `200` and as\nthe successor route.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"Validation error in one or more customers","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"BULK_VALIDATION_ERROR"},"message":{"type":"string"},"errors":{"type":"array","items":{"type":"object","properties":{"index":{"type":"integer","description":"Index of the customer with error (0-based)"},"field":{"type":"string"},"message":{"type":"string"}}}}}}}}]}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Customer Import"],"summary":"Delete several customers","description":"Deletes the customers listed in `ids`.\n\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/customers/bulk`, which behaves\n  identically.\n\n## Partial results\n\n- **Partial:** the customers that can be deleted are deleted, and the rest keep their\n  place in `customers_deletion` with the status that explains why.\n- **`HAS_INVOICES`:** a customer that has invoices cannot be deleted and comes back with\n  that row status.\n\n## What deleting means\n\n- **Semantics:** each deletion behaves as `DELETE /v1/customers/{customer_id}`. The\n  customer is retained for tax record-keeping purposes but is no longer exposed by the\n  API, its identifier is released for reuse, and invoices already issued to it keep their\n  own copy of the recipient's details.\n- **Not deactivating:** deleting frees the identifier, so the same NIF can be registered\n  again, while `PATCH` with `active: false` leaves the customer where it is with its NIF\n  still taken.\n\n**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.\n","operationId":"deactivateCustomersBulk","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/customers/bulk"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"name":"ids","in":"query","required":true,"description":"Comma-separated customer IDs","schema":{"type":"string","example":"550e8400-e29b-41d4-a716-446655440000,550e8400-e29b-41d4-a716-446655440001"}}],"responses":{"200":{"description":"Request processed. Always `200` when the batch was processed, **even if not a single\ncustomer could be deleted**: the status code says whether the request was understood\nand run, not whether every row went well. The per-row split is in the report.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerBulkDeleteResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers/import-csv-preview":{"post":{"tags":["Customer Import"],"summary":"Preview an import of customers from CSV","description":"Parses a CSV of customers and returns every row with its validation outcome, the\nrejected ones included.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/customers/imports/preview`, which\n  parses and validates the file without writing anything — what this route does by\n  default. To actually import, use `POST /v1/companies/{company_id}/customers/imports`,\n  where the origin of the file travels in the body as `source` and a boolean no longer\n  decides between looking and writing.\n- **File:** must use the headers of the template served by\n  `GET /v1/templates/customer-import`, and is limited to 5 MB and 1,000 rows. A file over\n  either limit is rejected as a whole with `400` (`CSV_FILE_TOO_LARGE` or\n  `TOO_MANY_RECORDS`) and nothing is imported.\n- **Validation:** each row is checked against the same rules as single-customer creation,\n  plus duplicate detection within the file and against existing customers, and a NIF\n  lookup in the AEAT register.\n- **`dry_run`:** at its default `true` nothing is persisted; `false` also persists the\n  valid customers.\n\n**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.\n","operationId":"importCustomersCsvPreview","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/customers/imports/preview"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"CSV file with customers to import"},"dry_run":{"type":"boolean","default":true,"description":"If true (recommended initially): preview only without persistence.\nIf false: preview + persistence of valid customers.\n"}}}}}},"responses":{"200":{"description":"CSV processing completed with structured preview using unified format","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"description":"Invalid file for one of the following reasons:\n- Format is not valid CSV\n- Encoding is not UTF-8\n- Required headers missing\n- Size exceeds 5MB (`CSV_FILE_TOO_LARGE`)\n- More than 1,000 records (`TOO_MANY_RECORDS`)\n\nWhich one it was travels in `error.message`. `error.details` may carry the same details\nas the canonical route; do not rely on it here — migrate.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"The uploaded file is over 12 MB (`FILE_TOO_LARGE`) and is rejected before it is read. A CSV over its own 5 MB limit answers `400` (`CSV_FILE_TOO_LARGE`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Structural CSV validation error (corrupted format, invalid encoding)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers/import-holded-contacts":{"post":{"tags":["Customer Import"],"summary":"Import customers from a Holded contacts export","description":"Parses a contacts export from Holded (`.xlsx`) as Holded produces it, maps each contact\nto a customer and returns every row with its validation outcome, as the CSV import does.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/customers/imports` with\n  `source: holded`, which imports the same file. It answers `201` and requires the\n  `Idempotency-Key` header.\n- **Mapping:** the contact name becomes `legal_name`, the Holded ID becomes the tax\n  identifier, and mobile takes precedence over landline for the phone.\n- **File:** limited to 10 MB and 5,000 contacts. A file over either limit is rejected as a\n  whole with `400` (`CSV_FILE_TOO_LARGE` or `TOO_MANY_RECORDS`) and nothing is imported.\n- **`preview`:** at its default `true` nothing is persisted; `false` also imports the\n  mapped customers.\n\n**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.\n","operationId":"importHoldedContacts","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/customers/imports"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Excel file (.xlsx) of contacts exported from Holded"},"preview":{"type":"boolean","default":true,"description":"If true: parsing and mapping only without importing to DB.\nIf false: parsing + mapping + actual import.\n"}}}}}},"responses":{"200":{"description":"Holded Excel processing completed with unified format.\nReturns parsed, validated and mapped customers using the same format as CSV import.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"The uploaded file is over 12 MB (`FILE_TOO_LARGE`) and is rejected before it is read; a\nfile over its own 10 MB limit answers `400` instead.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/customers":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyCustomers","tags":["CompanyCustomers"],"summary":"List the customers of a company","description":"Returns a paginated list of the customers of this company, with optional filters.\nOnly the customers of the company in the path are returned.\n","x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"active","in":"query","description":"Filter by active/inactive status. Defaults to `true`, so inactive customers must be\nrequested explicitly with `active=false`. Deleted customers are never returned by\neither value.\n","schema":{"type":"boolean"}},{"name":"search","in":"query","description":"Global search by name, NIF or email","schema":{"type":"string"}},{"name":"legal_name","in":"query","description":"Filter by legal name (partial search case-insensitive)","schema":{"type":"string"}},{"name":"nif","in":"query","description":"Filter by NIF (partial search)","schema":{"type":"string"}},{"name":"email","in":"query","description":"Filter by email (partial search)","schema":{"type":"string"}},{"name":"phone","in":"query","description":"Filter by phone (partial search)","schema":{"type":"string"}},{"name":"city","in":"query","description":"Filter by city","schema":{"type":"string"}},{"name":"province","in":"query","description":"Filter by province","schema":{"type":"string"}},{"name":"sort_by","in":"query","description":"Field to sort by. Results are always tie-broken by a stable internal key, so paging through the collection never repeats or skips a customer.","schema":{"allOf":[{"$ref":"#/components/schemas/CustomerSortBy"}],"default":"legal_name"}},{"name":"sort_order","in":"query","description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"asc"}}],"responses":{"200":{"description":"Customer list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["customers","pagination"],"properties":{"customers":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"examples":{"list_success":{"$ref":"#/components/examples/list-customers-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"createCompanyCustomer","tags":["CompanyCustomers"],"summary":"Create a customer for a company","description":"Creates a new customer under this company.\n\n- **`Idempotency-Key`:** it identifies the same operation on the deprecated flat route, so a\n  retry that switches route replays instead of creating twice.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCustomerRequest"},"examples":{"cliente_empresa":{"$ref":"#/components/examples/company-customer"},"cliente_autonomo":{"$ref":"#/components/examples/freelancer-customer"},"cliente_extranjero_ue":{"$ref":"#/components/examples/eu_customer"},"cliente_extranjero_usa":{"$ref":"#/components/examples/us_customer"},"cliente_spanish_business":{"$ref":"#/components/examples/spanish-business"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Customer created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]},"examples":{"created_success":{"$ref":"#/components/examples/created-success-2"}}}}},"400":{"$ref":"#/components/responses/InvalidJsonFormat"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`CLIENT_DUPLICATE` — a customer of this company already carries that NIF or alternative identifier. `details` is an empty object; the identifier is not echoed back.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"conflict":{"$ref":"#/components/examples/conflict-409"}}}}},"422":{"description":"The body parses but is not acceptable. `VALIDATION_ERROR` when a property breaks a\nconstraint (`details` maps each offending property to a message). A Spanish NIF is also\nchecked against the AEAT census: `NIF_NOT_IN_CENSUS` when the NIF does not exist there or,\nfor a natural person, `legal_name` does not match the name the census holds (no `details`).\nFor a company the census identifies the holder by its CIF alone, so the name is not verified.\nA NIF that is malformed answers `NIF_INVALID_FORMAT` or `NIF_INVALID_CONTROL_DIGIT`.\nSending both `nif` and `alternative_id` answers `ALTERNATIVE_ID_AND_NIF_EXCLUSIVE`: a\ncustomer is identified by exactly one of them.\n\nOther `error.code` values with this status:\n\n- `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` — the census identifies that Spanish `NOT_REGISTERED` (07) DNI or NIE with the given `legal_name`; send it in `nif` instead of `alternative_id`.\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"validation_error":{"$ref":"#/components/examples/validation-422"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/customers/bulk":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyCustomersBulk","x-no-location":"A bulk answer is a partial split (created and rejected), not one resource: there is no single URI to point at.","tags":["CompanyCustomers"],"summary":"Create several customers of a company","description":"Creates up to 500 customers of this company in a single call.\n\n- **Atomic:** if any customer fails validation the whole batch is rejected with `422`\n  `BULK_VALIDATION_ERROR` and nothing is persisted. This is not a partial operation.\n- **`dry_run`:** with `dry_run=true` the batch is only validated — tax identifiers against\n  the AEAT register, duplicates inside the batch and against the existing customers, field\n  formats — nothing is written and the answer is `200`. With `dry_run=false`, the default,\n  validation is followed by creation and the answer is `201`.\n- **Report:** both modes return the same per-record report, so a dry run and a real run are\n  read the same way.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"dry_run","in":"query","description":"Validate the batch without persisting it (`true`), or validate and create it (`false`,\nthe default). Either way the batch is atomic.\n","schema":{"type":"boolean","default":false}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customers"],"properties":{"customers":{"type":"array","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/CreateCustomerRequest"}}}},"examples":{"create_customers_bulk":{"$ref":"#/components/examples/create-multiple"}}}}},"responses":{"200":{"description":"Validation completed successfully (`dry_run=true`). Returns validation results without\npersisting data.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]},"examples":{"customers_bulk_created_success":{"$ref":"#/components/examples/bulk-created-success"}}}}},"201":{"description":"Customers created successfully (`dry_run=false`). Atomic operation: either every\ncustomer of the batch was created, or none was.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"description":"Validation error in one or more customers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyCustomersBulk","tags":["CompanyCustomers"],"summary":"Delete several customers of a company","description":"Deletes the customers listed in `ids` from this company.\n\n## Partial results\n\n- **Partial operation:** the customers that can be deleted are deleted, and the rest keep\n  their place in `customers_deletion` with the status that explains why. That is why it\n  answers `200` with a body instead of `204`, and why it answers `200` even when no row\n  could be deleted.\n- **`HAS_INVOICES`:** a customer that has invoices cannot be deleted and comes back with\n  that row status.\n\n## What deleting means\n\n- **Semantics:** the same semantics as\n  `DELETE /v1/companies/{company_id}/customers/{customer_id}` — the customer is retained\n  internally for tax record-keeping purposes but is no longer exposed by the API, its\n  identifier is released for reuse, and invoices already issued to it keep their own copy of\n  the recipient's details.\n- **Deleting is not deactivating:** deleting frees the identifier, so the same NIF can be\n  registered again, while `PATCH` with `active: false` leaves the customer where it is with\n  its NIF still taken.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"name":"ids","in":"query","required":true,"description":"Comma-separated customer IDs","schema":{"type":"string","example":"550e8400-e29b-41d4-a716-446655440000,550e8400-e29b-41d4-a716-446655440001"}}],"responses":{"200":{"description":"Request processed. Always `200` when the batch was processed, **even if not a single\ncustomer could be deleted**: the status code says whether the request was understood\nand run, not whether every row went well. The per-row split is in the report.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerBulkDeleteResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/customers/imports":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyCustomerImport","x-no-location":"An import creates many customers (or none, on dry_run): there is no single URI to point at.","tags":["CompanyCustomerImports"],"summary":"Import customers into a company","description":"Imports customers into this company from an uploaded file.\n\n- **`source`:** the origin travels in the body. `csv` is a file following the import\n  template, limited to 5 MB and 1,000 records; `holded` is an Excel (`.xlsx`) exported from\n  Holded contacts, limited to 10 MB and 5,000 records. A file over either limit of its\n  `source` is rejected as a whole with `400` (`CSV_FILE_TOO_LARGE` or `TOO_MANY_RECORDS`)\n  and nothing is imported. A file over 12 MB answers `413`\n  (`FILE_TOO_LARGE`) before it is read. A `holded` file that does not open as a workbook\n  answers `422 INVALID_EXCEL_FORMAT`, and one whose columns are not those of a Holded\n  contacts export `422 INVALID_EXCEL_STRUCTURE`.\n- **This operation writes:** the customers it accepts are created, and a record whose tax\n  identifier already exists in this company is reported as a duplicate rather than created\n  again, so re-importing the same file duplicates nothing.\n- **`Idempotency-Key`:** required on this operation.\n- **Rehearsal:** to see what would happen without writing anything, use\n  `POST .../customers/imports/preview`, a separate operation with no effects at all — the\n  import is never governed by a boolean flag.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"parameters":[{"$ref":"#/components/parameters/RequiredIdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","source"],"properties":{"file":{"type":"string","format":"binary","description":"File with the customers to import, in the format of `source`"},"source":{"$ref":"#/components/schemas/CustomerImportSource"}}}}}},"responses":{"201":{"description":"Import completed. Returns the outcome per record, in the same format as the preview.\n`statistics.imported` is how many customers were actually created; every other counter\nclassifies the submitted rows.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"$ref":"#/components/responses/CustomerImportRejected"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"The uploaded file is over 12 MB (`FILE_TOO_LARGE`) and is rejected before it is read; a\nfile over the limit of its `source` answers `400` instead.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/customers/imports/preview":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"previewCompanyCustomerImport","tags":["CompanyCustomerImports"],"summary":"Preview an import of customers into a company","description":"Parses and validates the file without writing anything: no customer is created. It returns\nthe same per-record outcome the import would produce, so the caller can correct the data\nbefore importing it with `POST .../customers/imports`.\n\n- **`source`:** the origin travels in the body, exactly as in the import.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","source"],"properties":{"file":{"type":"string","format":"binary","description":"File with the customers to preview, in the format of `source`"},"source":{"$ref":"#/components/schemas/CustomerImportSource"}}}}}},"responses":{"200":{"description":"Preview completed. Nothing was written, so `statistics.imported` is always `0` here;\n`statistics.importable` is what a real import would create.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CustomerValidationUnifiedResult"}}}]}}}},"400":{"$ref":"#/components/responses/CustomerImportRejected"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"The uploaded file is over 12 MB (`FILE_TOO_LARGE`) and is rejected before it is read; a\nfile over the limit of its `source` answers `400` instead.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/customers/{customer_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"customer_id","in":"path","required":true,"description":"Customer ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanyCustomer","tags":["CompanyCustomers"],"summary":"Get a customer of a company","description":"Retrieves the complete details of a customer of this company.","x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"Customer retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]},"examples":{"get_success":{"$ref":"#/components/examples/get-customer-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"There is no such customer in this company. It never refers to the NIF in the path:\na NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyCustomer","tags":["CompanyCustomers"],"summary":"Update a customer of a company partially","description":"Updates only the fields present in the body, leaving every other field of the customer as it\nis.\n\n- **Null vs omitted:** a field sent as `null` is cleared, which is different from omitting\n  it (see `PatchCustomerRequest`).\n- **AEAT census:** the customer's Spanish tax identifier is only checked against the AEAT\n  census when the request changes the `nif` or the `legal_name`. Editing anything else —\n  phone, notes, address, billing emails — never asks the census, so a customer stored long\n  ago stays editable even if its NIF is no longer listed.\n  For a **legal entity** the census checks the CIF only and its `legal_name` is not\n  verified, so the name never causes the rejection; only a **natural person**'s NIF is\n  crossed with the name.\n  Duplicate and format checks run on every update.\n- **Only update verb:** this is the canonical way to edit a customer. There is no `PUT` of\n  full replacement under the company, which would clear the fields you omit.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchCustomerRequest"},"examples":{"update_customer_address":{"$ref":"#/components/examples/update_address"},"update_customer_contact":{"$ref":"#/components/examples/update_contact"},"deactivate_customer":{"$ref":"#/components/examples/deactivate_customer"}}}}},"responses":{"200":{"description":"Customer updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Customer"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidJsonFormat"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"There is no such customer in this company. It never refers to the NIF in the path:\na NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate customer (same tax identifier), or another request modified this customer\nfirst: `error.code` is `CONCURRENT_MODIFICATION` and no change was applied. Retry\nthe request over the current state of the customer.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE` — an `alternative_id` of type `NOT_REGISTERED` with country `ES` must be a DNI or NIE; a company (CIF) is always registered, send it as `nif`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyCustomer","tags":["CompanyCustomers"],"summary":"Delete a customer of a company","description":"Deletes a customer of this company that has no invoices.\n\n- **What deleting means:** the customer is retained internally for tax record-keeping\n  purposes, but is no longer exposed by the API: subsequent requests to it return `404`, and\n  it is never included in the customer list, under any value of the `active` filter.\n- **Identifier released:** its NIF or alternative identifier is freed, so a new customer may\n  be created with the same identifier.\n- **Customers with invoices:** they cannot be deleted and the request answers `409`\n  `CLIENT_HAS_INVOICES`. To stop using a customer, update it with `active` set to `false`\n  instead of deleting it.\n- **Customers used by a recurring invoice:** while an active or paused recurring invoice\n  has the customer as its recipient, the request answers `400`\n  `REFERENCED_BY_RECURRING_INVOICE`.\n","x-required-scopes":["customers:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:write"]}],"responses":{"204":{"description":"Customer deleted. No body — there is nothing to report that the request itself does not already imply."},"400":{"description":"An active or paused recurring invoice uses this customer as its recipient\n(`REFERENCED_BY_RECURRING_INVOICE`). The customer is left untouched. End that recurring\ninvoice or change its customer first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"There is no such customer in this company. It never refers to the NIF in the path:\na NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The customer has invoices (`CLIENT_HAS_INVOICES`). The customer is left untouched — it\nis neither deleted nor deactivated.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/customers/templates/csv":{"post":{"tags":["Customer Templates"],"summary":"Download the customer import template","deprecated":true,"x-deprecation":"nif-migration","description":"Downloads a sample CSV for customer import: the required headers plus example rows, written\nwith a UTF-8 byte order mark so that Excel opens it with the accents intact.\n\n- **Deprecated:** use `GET /v1/templates/customer-import`, which returns the same file.\n  Reading a fixed template is not a `POST`, and the template belongs to no customer.\n- **Retirement:** the response announces the retirement date in its `Sunset` header.\n\n**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.\n","operationId":"downloadCustomerTemplateCsv","x-successor":{"method":"GET","path":"/v1/templates/customer-import"},"x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"CSV template downloaded successfully","content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"nombre_fiscal;nif;email;telefono;direccion_calle;direccion_numero;direccion_codigo_postal;direccion_poblacion;direccion_provincia\n\"Construcciones García y Asociados SL\";B12345674;contacto@construccionesgarcia.es;915551234;\"Calle Gran Vía\";45;28013;Madrid;Madrid\n"}},"headers":{"Content-Disposition":{"description":"File name for download","schema":{"type":"string","example":"attachment; filename=\"template_clientes_beel.csv\""}},"Content-Type":{"description":"File MIME type","schema":{"type":"string","example":"text/csv; charset=utf-8"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/templates/customer-import":{"get":{"tags":["Templates"],"summary":"Download the customer import template","description":"Downloads the sample CSV that `POST /v1/companies/{company_id}/customers/imports` expects\nwith `source: csv`: the required headers plus example rows, separated by semicolons and written with a UTF-8 byte order mark\nso that Excel opens it with the accents intact.\n\n- **Example rows:** every one is importable as it stands — commas, quotes and accents\n  included — so the template can be uploaded unchanged as a first test of the import.\n- **Scope:** the file is the same for every credential and does not depend on any account\n  or on any NIF.\n","operationId":"downloadCustomerImportTemplate","x-required-scopes":["customers:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["customers:read"]}],"responses":{"200":{"description":"CSV template downloaded successfully","content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"nombre_fiscal;nif;email;telefono;direccion_calle;direccion_numero;direccion_codigo_postal;direccion_poblacion;direccion_provincia\n\"Construcciones García y Asociados SL\";B12345674;contacto@construccionesgarcia.es;915551234;\"Calle Gran Vía\";45;28013;Madrid;Madrid\n"}},"headers":{"Content-Disposition":{"description":"File name for download","schema":{"type":"string","example":"attachment; filename=\"template_clientes_beel.csv\""}},"Content-Type":{"description":"File MIME type","schema":{"type":"string","example":"text/csv; charset=utf-8"}},"Cache-Control":{"description":"The file only changes when the import format does, so it may be kept for an hour. It\nis `private` and not `public` because the response is served under a credential: a\nshared cache must not hold it.\n","schema":{"type":"string","example":"private, max-age=3600"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/templates/account-import":{"get":{"tags":["Templates"],"summary":"Download the managed-account import template","description":"Downloads the sample CSV that `POST /v1/accounts/imports` expects: the required headers plus\none example row, separated by semicolons and written with a UTF-8 byte order mark so that\nExcel opens it with the accents intact — the same conventions as\n`GET /v1/templates/customer-import`.\n\n- **The example row is deliberately not importable:** its tax id has a valid shape and an\n  impossible check digit, so it is rejected instead of silently provisioning an account.\n  Replace it with your own rows.\n- **`customers_file`:** the optional second file of that import is **not** a new format; it\n  is the customer import template, unchanged. Download it from\n  `GET /v1/templates/customer-import`.\n- **Scope:** the file is the same for every credential and does not depend on any account\n  or on any NIF.\n","operationId":"downloadAccountImportTemplate","x-required-scopes":["accounts:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:read"]}],"responses":{"200":{"description":"CSV template downloaded successfully","content":{"text/csv":{"schema":{"type":"string","format":"binary"},"example":"external_ref;email;display_name;nif;legal_name;entity_type;direccion_calle;direccion_numero;direccion_codigo_postal;direccion_poblacion;direccion_provincia;irpf;iban;legal_form;trade_name;idioma\n10042;socio@ejemplo.es;\"EJEMPLO - BORRA ESTA FILA\";00000000A;\"EJEMPLO - BORRA ESTA FILA\";LEGAL_ENTITY;\"Calle Mayor\";12;43330;Riudoms;Tarragona;15;ES9121000418450200051332;SL;\"Transportes Ejemplo\";es\n"}},"headers":{"Content-Disposition":{"description":"File name for download","schema":{"type":"string","example":"attachment; filename=\"plantilla_cuentas_beel.csv\""}},"Content-Type":{"description":"File MIME type","schema":{"type":"string","example":"text/csv; charset=utf-8"}},"Cache-Control":{"description":"The file only changes when the import format does, so it may be kept for an hour. It\nis `private` and not `public` because the response is served under a credential: a\nshared cache must not hold it.\n","schema":{"type":"string","example":"private, max-age=3600"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/products":{"get":{"tags":["Products"],"summary":"List the products","description":"Returns a paginated list of products and services, with optional filters.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/products`, which returns the same\n  list. The free-text filter is named `q` there, not `search`.\n\n**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.\n","operationId":"listProducts","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/products"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"category","in":"query","description":"Filter by product category","schema":{"$ref":"#/components/schemas/ProductCategory"}},{"name":"active","in":"query","description":"Filter by active/inactive status","schema":{"type":"boolean"}},{"name":"search","in":"query","description":"Global search by name, code or description","schema":{"type":"string","maxLength":100}},{"name":"name","in":"query","description":"Filter by name (partial search case-insensitive)","schema":{"type":"string"}},{"name":"code","in":"query","description":"Filter by code (partial search)","schema":{"type":"string"}},{"name":"min_price","in":"query","description":"Minimum price","schema":{"type":"number","minimum":0}},{"name":"max_price","in":"query","description":"Maximum price","schema":{"type":"number","minimum":0}},{"name":"sort_by","in":"query","description":"Field to sort by","schema":{"allOf":[{"$ref":"#/components/schemas/ProductSortBy"}],"default":"name"}},{"name":"sort_order","in":"query","description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"asc"}}],"responses":{"200":{"description":"Product list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["products","pagination"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["Products"],"summary":"Create a product","description":"Creates a new product or service in the catalog.\n\n- **Deprecated:** use `POST /v1/companies/{company_id}/products`, which behaves\n  identically.\n\n**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.\n","operationId":"createProduct","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/products"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductRequest"}}}},"responses":{"201":{"description":"Product created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Duplicate code or request already processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `REGIME_REQUIRES_SURCHARGE` — a product with explicit `regime_key: \"18\"` has no `equivalence_surcharge_rate` > 0; add the surcharge or omit the key.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/products/{product_id}":{"parameters":[{"name":"product_id","in":"path","required":true,"description":"Product unique UUID","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Products"],"summary":"Get a product","description":"Retrieves the details of a product.\n\n- **Deprecated:** use `GET /v1/companies/{company_id}/products/{product_id}`, which behaves\n  identically.\n\n**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.\n","operationId":"getProduct","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/products/{product_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:read"]}],"responses":{"200":{"description":"Product retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["Products"],"summary":"Replace a product","description":"Replaces an existing product with the body you send.\n\n- **Not a partial update:** leaving out `main_tax`, `equivalence_surcharge_rate` or\n  `irpf_rate` resets them to the creation defaults (IVA 21%, 0% and 0%), so send the product\n  complete. To change only some fields, use\n  `PATCH /v1/companies/{company_id}/products/{product_id}`.\n- **Deprecated:** this route will be retired on the date announced in its `Sunset` response\n  header. The canonical form has a single update verb,\n  `PATCH /v1/companies/{company_id}/products/{product_id}`, which is not a drop-in\n  replacement for this one: it changes only the fields present in the body and resets\n  nothing on its own. To reproduce a total replacement, send every field and pass `null` in\n  the ones you want cleared.\n\n**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.\n","operationId":"updateProduct","deprecated":true,"x-deprecation":"nif-migration","x-no-successor":"The canonical form only has PATCH, which leaves absent fields untouched instead of resetting them to the creation defaults: it is not a drop-in destination for this route. Migration guidance is in the description — send every field and pass null in the ones you want cleared.","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductRequest"}}}},"responses":{"200":{"description":"Product updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `REGIME_REQUIRES_SURCHARGE` — a product with explicit `regime_key: \"18\"` has no `equivalence_surcharge_rate` > 0; add the surcharge or omit the key.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["Products"],"summary":"Update a product partially","description":"Updates only the fields present in the body, leaving every other field of the product as it\nis — in particular `main_tax`, `irpf_rate` and `equivalence_surcharge_rate`, which `PUT`\nresets.\n\n- **Null vs omitted:** a field sent as `null` is cleared, which is different from omitting\n  it (see `PatchProductRequest`). The result goes through the same validation as `PUT`.\n- **Deprecated:** use `PATCH /v1/companies/{company_id}/products/{product_id}`, which\n  behaves identically.\n\n**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.\n","operationId":"patchProduct","x-successor":{"method":"PATCH","path":"/v1/companies/{company_id}/products/{product_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchProductRequest"}}}},"responses":{"200":{"description":"Product updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Duplicate code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `REGIME_REQUIRES_SURCHARGE` — a product with explicit `regime_key: \"18\"` has no `equivalence_surcharge_rate` > 0; add the surcharge or omit the key.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Products"],"summary":"Delete a product","description":"Deletes a product from the catalog.\n\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/products/{product_id}`, which\n  behaves identically.\n\n**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.\n","operationId":"deleteProduct","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/products/{product_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"responses":{"204":{"description":"Product deleted successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/products/search":{"get":{"tags":["Products"],"summary":"Search the products","description":"Searches the active products by name or code and returns at most 20 of them, for\nautocomplete.\n\n- **Deprecated — withdrawn, not moved:** its replacement is\n  `GET /v1/companies/{company_id}/products?q=`, which searches the name, the code **and**\n  the description, so it returns at least everything this endpoint returned.\n- **Response shape:** it differs, and that is why this notice exists. Here `data` is a plain\n  array of products, limited to 20 active ones, while there `data` is the paginated envelope\n  of the list (`data.products` + `data.pagination`). Change the way you read the response\n  when you migrate.\n\n**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.\n","operationId":"searchProducts","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/products"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:read"]}],"parameters":[{"name":"search","in":"query","description":"Search term (empty to get recent products).\n\nThis parameter was previously named `q`. The old name is still accepted for backwards\ncompatibility (see `q` below) and will be withdrawn in a future major version — send\n`search`.\n","required":false,"schema":{"type":"string","maxLength":100}},{"name":"q","in":"query","required":false,"deprecated":true,"description":"**Deprecated** — former name of `search`, still honoured so existing integrations keep\nworking. Ignored when `search` is also present. Use `search`.\n","schema":{"type":"string","maxLength":100}},{"name":"limit","in":"query","description":"Result limit (max 20)","schema":{"type":"integer","minimum":1,"maximum":20,"default":10}}],"responses":{"200":{"description":"Search results retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Product"}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/products/bulk":{"post":{"tags":["Products"],"summary":"Create several products","description":"Creates up to 100 products in one call.\n\n- **Partial operation:** each product is processed and reported independently, so a row the\n  domain rejects — a rate BeeL does not accept, a duplicate code — comes back inside the\n  report while the rest are created.\n- **Status code:** always `201` when the batch was processed, even if not a single product\n  could be created. A malformed request — a missing field, an empty array, more than 100\n  items — answers `422` instead and nothing is processed.\n- **Deprecated:** use `POST /v1/companies/{company_id}/products/bulk`, which behaves\n  identically.\n\n**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.\n","operationId":"createProductsBulk","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/products/bulk"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["products"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/CreateProductRequest"},"minItems":1,"maxItems":100}}}}}},"responses":{"201":{"description":"Request processed. Always `201` when the batch was processed, **even if not a single\nproduct could be created**: the status code says whether the request was understood and\nrun, not whether every row went well. The per-row split is in the report.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProductBulkCreateResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The **request** is wrong, so nothing was processed: a required field missing, a value of\nthe wrong type, an empty array, more than `maxItems`. `error.details` names the offending\nfield **with its index** (`products[1].main_tax.percentage`). A row that only the domain\ncan reject — a rate BeeL does not accept, a duplicate code — is NOT this: it comes back\ninside the report of the `201`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Products"],"summary":"Delete several products","description":"Deletes up to 100 products of the catalog, listed in `product_ids`.\n\n- **Partial operation:** the response reports which products were deleted\n  (`deleted_products`) and which failed (`errors`, one entry per product with its\n  `product_id`), with the counts in `summary`. That is why it answers `200` with a body\n  instead of `204`.\n- **Deprecated:** use `DELETE /v1/companies/{company_id}/products/bulk`, which deletes the\n  same way but takes the IDs in the `ids` query parameter instead of in the request body: a\n  `DELETE` body has no defined semantics and intermediaries may drop it.\n\n**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.\n","operationId":"deleteProductsBulk","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/products/bulk"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["product_ids"],"properties":{"product_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":100}}}}}},"responses":{"200":{"description":"Bulk delete operation completed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted_products":{"type":"array","items":{"type":"string","format":"uuid"}},"errors":{"type":"array","items":{"type":"object","properties":{"product_id":{"type":"string","format":"uuid"},"error":{"type":"string"}}}},"summary":{"type":"object","properties":{"total_processed":{"type":"integer"},"successful":{"type":"integer"},"failed":{"type":"integer"}}}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/products":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyProducts","tags":["CompanyProducts"],"summary":"List the products of a company","description":"Returns a paginated list of the products/services of this company, with optional\nfilters.\n\n- **`q`:** searching is done on this collection, there is no separate search path. `q`\n  matches the name, the code and the description, so it returns at least everything the\n  withdrawn `GET /v1/products/search` returned, in the paginated envelope of this list.\n","x-required-scopes":["products:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"q","in":"query","description":"Search by name, code or description","schema":{"type":"string","maxLength":100}},{"name":"category","in":"query","description":"Filter by product category","schema":{"$ref":"#/components/schemas/ProductCategory"}},{"name":"active","in":"query","description":"Filter by active/inactive status","schema":{"type":"boolean"}},{"name":"name","in":"query","description":"Filter by name (partial search case-insensitive)","schema":{"type":"string"}},{"name":"code","in":"query","description":"Filter by code (partial search)","schema":{"type":"string"}},{"name":"min_price","in":"query","description":"Minimum price","schema":{"type":"number","minimum":0}},{"name":"max_price","in":"query","description":"Maximum price","schema":{"type":"number","minimum":0}},{"name":"sort_by","in":"query","description":"Field to sort by","schema":{"allOf":[{"$ref":"#/components/schemas/ProductSortBy"}],"default":"name"}},{"name":"sort_order","in":"query","description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"asc"}}],"responses":{"200":{"description":"Product list retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["products","pagination"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"examples":{"products_list_success":{"$ref":"#/components/examples/list-products-success"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"createCompanyProduct","tags":["CompanyProducts"],"summary":"Create a product for a company","description":"Creates a product or service in the catalog of the company in the path and returns it with\nits `id`. A product holds reusable defaults for invoice lines: name, price, unit and taxes.\n\n- **Required:** only `name`. Omitted, `category` is `SERVICE`, and the product is created\n  active.\n- **`code`:** optional, and unique within the company. A code another product of the\n  company already uses answers `409` with `PRODUCT_DUPLICATE`.\n- **Taxes:** `equivalence_surcharge_rate` has to be coherent with `main_tax.regime_key`;\n  an incoherent pair answers `422` (see the field).\n- **Several at once:** `POST /v1/companies/{company_id}/products/bulk` creates up to 100\n  in one call.\n","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductRequest"},"examples":{"physical_product":{"$ref":"#/components/examples/physical_product"},"service_product":{"$ref":"#/components/examples/service_product"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Product created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]},"examples":{"product_created_success":{"$ref":"#/components/examples/created-success-3"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate code or request already processed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `REGIME_REQUIRES_SURCHARGE` — a product with explicit `regime_key: \"18\"` has no `equivalence_surcharge_rate` > 0; add the surcharge or omit the key.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/products/bulk":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompanyProductsBulk","x-no-location":"A bulk answer is a partial split (created and rejected), not one resource: there is no single URI to point at.","tags":["CompanyProducts"],"summary":"Create several products for a company","description":"Creates up to 100 products in the catalog of this company.\n\n- **Partial operation:** each product is processed and reported independently, so a row the\n  domain rejects — a rate BeeL does not accept, a duplicate code — comes back inside the\n  report while the rest are created.\n- **Status code:** always `201` when the batch was processed, even if not a single product\n  could be created. A malformed request — a missing field, an empty array, more than 100\n  items — answers `422` instead and nothing is processed.\n","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["products"],"properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/CreateProductRequest"},"minItems":1,"maxItems":100}}},"examples":{"create_products_bulk":{"$ref":"#/components/examples/create-multiple-2"}}}}},"responses":{"201":{"description":"Request processed. Always `201` when the batch was processed, **even if not a single\nproduct could be created**: the status code says whether the request was understood and\nrun, not whether every row went well. The per-row split is in the report.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProductBulkCreateResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The **request** is wrong, so nothing was processed: a required field missing, a value of\nthe wrong type, an empty array, more than `maxItems`. `error.details` names the offending\nfield **with its index** (`products[1].main_tax.percentage`). A row that only the domain\ncan reject — a rate BeeL does not accept, a duplicate code — is NOT this: it comes back\ninside the report of the `201`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyProductsBulk","tags":["CompanyProducts"],"summary":"Delete several products of a company","description":"Deletes the products listed in `ids` from the catalog of this company, up to 100 IDs\nper request; send several requests for more.\n\n- **Partial operation:** the response reports which products were deleted\n  (`deleted_products`) and which failed (`errors`, one entry per product with its\n  `product_id`), with the counts in `summary`. That is why it answers `200` with a body\n  instead of `204`.\n","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"parameters":[{"name":"ids","in":"query","required":true,"description":"Comma-separated product IDs (max 100 per request)","schema":{"type":"string","example":"550e8400-e29b-41d4-a716-446655440000,550e8400-e29b-41d4-a716-446655440001"}}],"responses":{"200":{"description":"Bulk delete operation completed","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"deleted_products":{"type":"array","items":{"type":"string","format":"uuid"}},"errors":{"type":"array","items":{"type":"object","properties":{"product_id":{"type":"string","format":"uuid"},"error":{"type":"string"}}}},"summary":{"type":"object","properties":{"total_processed":{"type":"integer"},"successful":{"type":"integer"},"failed":{"type":"integer"}}}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"Payload too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/products/{product_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"product_id","in":"path","required":true,"description":"Product unique UUID","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyProduct","tags":["CompanyProducts"],"summary":"Get a product of a company","description":"Retrieves one product of the catalog of this company. A product of another company answers `404` with `PRODUCT_NOT_FOUND`, exactly like one that does not exist or was deleted.","x-required-scopes":["products:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:read"]}],"responses":{"200":{"description":"Product retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No product with this `product_id` exists in this company. It never refers to the\nNIF in the path: a NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyProduct","tags":["CompanyProducts"],"summary":"Update a product of a company partially","description":"Updates only the fields present in the body, leaving every other field of the product as it\nis — in particular `main_tax`, `irpf_rate` and `equivalence_surcharge_rate`.\n\n- **Null vs omitted:** a field sent as `null` is cleared, which is different from omitting\n  it (see `PatchProductRequest`).\n- **Only update verb:** the total replacement `PUT /v1/products/{product_id}`, which reset\n  the omitted fields to their creation defaults, is not carried over to the canonical\n  form.\n","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchProductRequest"},"examples":{"update_product_price":{"$ref":"#/components/examples/update_price"},"update_product_tax":{"$ref":"#/components/examples/update_tax"},"deactivate_product":{"$ref":"#/components/examples/deactivate_product"}}}}},"responses":{"200":{"description":"Product updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No product with this `product_id` exists in this company. It never refers to the\nNIF in the path: a NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate code, or another request modified this product first: `error.code` is\n`CONCURRENT_MODIFICATION` and no change was applied. Retry the request over the\ncurrent state of the product.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable entity - validation or business rule error\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `INVALID_SURCHARGE` — the `equivalence_surcharge_rate` is not an equivalence surcharge rate (see `EquivalenceSurchargePercentage`); send one of the published values.\n- `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.\n- `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.\n- `REGIME_REQUIRES_SURCHARGE` — a product with explicit `regime_key: \"18\"` has no `equivalence_surcharge_rate` > 0; add the surcharge or omit the key.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyProduct","tags":["CompanyProducts"],"summary":"Delete a product of a company","description":"Deletes a product from the catalog of this company. It no longer appears in the list and reading it answers `404`. Invoices already created keep their lines unchanged: a line copies its data and does not point to the product.","x-required-scopes":["products:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["products:write"]}],"responses":{"204":{"description":"Product deleted successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No product with this `product_id` exists in this company. It never refers to the\nNIF in the path: a NIF you cannot reach answers `403`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/members":{"parameters":[{"$ref":"#/components/parameters/AccountIdPath"}],"get":{"tags":["Members"],"summary":"List the account's members","description":"Lists the people with access to the account, each with their `account_role` and, for\n`MEMBER`s, the companies (NIFs) granted to them.\n\n**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get\nthe first 20 members, not all of them. Read `data.pagination` to walk the rest.\n","operationId":"listAccountMembers","x-required-scopes":["members:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"Members retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["members","pagination"],"properties":{"members":{"type":"array","items":{"$ref":"#/components/schemas/AccountMember"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagement"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/members/{member_id}":{"parameters":[{"$ref":"#/components/parameters/AccountIdPath"},{"$ref":"#/components/parameters/MemberIdPath"}],"get":{"tags":["Members"],"summary":"Retrieve a member","description":"Returns one member of the account, with the same shape the list returns.","operationId":"getAccountMember","x-required-scopes":["members:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:read"]}],"responses":{"200":{"description":"Member retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AccountMember"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagement"},"404":{"$ref":"#/components/responses/MemberNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["Members"],"summary":"Change a member's account role","description":"Changes a member's `account_role` between `ADMIN` and `MEMBER`.\n\n- **`OWNER`:** not an assignable value here. An account has exactly one owner, and\n  ownership is handed over only through `PUT /v1/accounts/{account_id}/owner`, which\n  promotes the new owner and steps the current one down in the same operation.\n- **Last owner:** the account's last `OWNER` cannot be demoted.\n","operationId":"patchAccountMember","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeMemberRoleRequest"},"examples":{"change_member_role_admin":{"$ref":"#/components/examples/member-role-admin"}}}}},"responses":{"200":{"description":"Member role updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AccountMember"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOwnerReserved"},"404":{"$ref":"#/components/responses/MemberNotFound"},"422":{"description":"Validation error. `error.code` is `OWNER_ROLE_NOT_ASSIGNABLE` (`account_role` was `OWNER`; use `PUT /v1/accounts/{account_id}/owner` instead — returned to every caller, owners included, and also when the member already is the owner) or `LAST_OWNER_PROTECTED` (the change would demote the account's only owner).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Members"],"summary":"Remove a member from the account","description":"Removes a member's access to the account. The account's last `OWNER` cannot be removed.","operationId":"deleteAccountMember","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"responses":{"204":{"description":"Member removed successfully"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenOwnerReserved"},"404":{"$ref":"#/components/responses/MemberNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/members/{member_id}/grants":{"parameters":[{"$ref":"#/components/parameters/AccountIdPath"},{"$ref":"#/components/parameters/MemberIdPath"}],"get":{"tags":["Members"],"summary":"List a member's company grants","description":"Lists the companies (NIFs) granted to a `MEMBER` and the `access_level` of each. Empty for\n`OWNER` and `ADMIN`, who reach every company of the account implicitly and hold no grants.\n\n**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get\nthe first 20 grants, not all of them. Read `data.pagination` to walk the rest.\n","operationId":"listAccountMemberGrants","x-required-scopes":["members:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"Grants retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["grants","pagination"],"properties":{"grants":{"type":"array","items":{"$ref":"#/components/schemas/MemberGrant"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagement"},"404":{"$ref":"#/components/responses/MemberNotFound"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/members/{member_id}/grants/{company_id}":{"parameters":[{"$ref":"#/components/parameters/AccountIdPath"},{"$ref":"#/components/parameters/MemberIdPath"},{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company within the account.","schema":{"type":"string","format":"uuid"}}],"put":{"tags":["Members"],"summary":"Grant a member access to a company","description":"Grants a `MEMBER` access to one company, or changes the `access_level` of an existing\ngrant. Only the company in the path is touched.\n\n- **Scope:** the member's other grants are left exactly as they were.\n- **`access_level`:** `VIEW` or `OPERATE`. `NONE` is not accepted here — remove access by\n  deleting the grant.\n- **Eligible members:** grants apply only to `MEMBER`. `OWNER` and `ADMIN` reach every\n  company implicitly and cannot receive grants.\n","operationId":"putAccountMemberGrant","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PutMemberGrantRequest"},"examples":{"put_member_grant_operate":{"$ref":"#/components/examples/member-grant-operate"}}}}},"responses":{"200":{"description":"Grant created or updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/GrantAssignment"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagementLiveCredentialRequired"},"404":{"$ref":"#/components/responses/MemberGrantNotFound"},"422":{"description":"Validation error. `error.code` is `GRANTS_ONLY_FOR_MEMBER` (the target member is not a `MEMBER`) or `GRANT_COMPANY_NOT_IN_ACCOUNT` (the company does not belong to the account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Members"],"summary":"Revoke a member's access to a company","description":"Revokes a `MEMBER`'s access to one company. Their grants over the account's other companies are left as they were.","operationId":"deleteAccountMemberGrant","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"responses":{"204":{"description":"Grant revoked successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagementLiveCredentialRequired"},"404":{"$ref":"#/components/responses/MemberGrantNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/owner":{"parameters":[{"$ref":"#/components/parameters/AccountIdPath"}],"put":{"tags":["Members"],"summary":"Set the account owner","description":"Makes the member in the body the account's `OWNER` and steps the calling owner down to\n`ADMIN`, so the account always keeps exactly one owner.\n\n- **Repeatable:** it states the desired owner rather than performing a transfer, so\n  repeating the same call once that member already owns the account returns the same\n  `204` instead of failing.\n- **Credentials:** unlike the rest of this API, this operation is available only from a\n  signed-in dashboard session. No API key, live or test, can perform it, whatever scopes\n  it holds.\n- **From an integration:** to hand an account over to its holder, issue a claim token with\n  `POST /v1/accounts/{account_id}/claim-tokens` instead.\n","operationId":"putAccountOwner","security":[{"SessionCookie":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAccountOwnerRequest"},"examples":{"set_account_owner":{"$ref":"#/components/examples/set-owner"}}}}},"responses":{"204":{"description":"Ownership set successfully"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenSessionOnly"},"404":{"$ref":"#/components/responses/MemberNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/invitations":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Invitations"],"summary":"List the account's invitations","description":"Lists the invitations sent to join the account, whatever their `status`. Accepted, revoked and expired invitations stay in the list: the record is the trail of who was granted access to the account's fiscal data.","operationId":"listAccountInvitations","x-required-scopes":["members:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"Invitations retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["invitations","pagination"],"properties":{"invitations":{"type":"array","items":{"$ref":"#/components/schemas/InvitationSummary"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagement"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["Invitations"],"summary":"Create an invitation","description":"Creates a single-use invitation for a person to join the account with the given\n`account_role`.\n\n- **`token`:** the acceptance secret, returned once and never readable again, so deliver\n  it to the invitee. `invitation_url` is the ready-to-use link built from that same token.\n- **`grants`:** the companies a `MEMBER` starts with. Omit it, or send `[]`, to invite\n  them with no company access yet; an explicit `null` is rejected with `422`. Grants are only valid for `MEMBER`, since `OWNER` and\n  `ADMIN` reach every company implicitly.\n- **`account_role`:** `OWNER` cannot be invited. An account has exactly one owner, handed\n  over only through `PUT /v1/accounts/{account_id}/owner`.\n- **`send_email`:** defaults to `false`, so BeeL sends no email and you deliver the token\n  or `invitation_url` yourself. Set it to `true` to have the invitation emailed to\n  `invited_email` as well.\n","operationId":"createAccountInvitation","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvitationRequest"},"examples":{"create_invitation_member":{"$ref":"#/components/examples/invitation-member"},"create_invitation_admin":{"$ref":"#/components/examples/invitation-admin"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Invitation created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Invitation"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagementLiveCredentialRequired"},"409":{"description":"`MEMBER_ALREADY_IN_ACCOUNT` — the invited person already belongs to this account, so there is nothing to invite. Change their role with `PATCH /v1/accounts/{account_id}/members/{member_id}` instead.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"MEMBER_ALREADY_IN_ACCOUNT","message":"That person already belongs to this account."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"422":{"description":"Validation error. `error.code` is `OWNER_ROLE_NOT_ASSIGNABLE` (`account_role` was `OWNER`; returned to every caller, owners included), `GRANTS_ONLY_FOR_MEMBER` (grants provided with `account_role` other than `MEMBER`) or `GRANT_COMPANY_NOT_IN_ACCOUNT` (a grant's company does not belong to the account).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/invitations/{invitation_id}":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}},{"name":"invitation_id","in":"path","required":true,"description":"Identifier (UUID) of an invitation of the account in the path, as returned when it is created or listed. An invitation of another account answers `404`, exactly like one that does not exist.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Invitations"],"summary":"Retrieve an invitation","description":"Returns one invitation of the account, with the same shape the list returns. An invitation stays readable for its whole life: `ACCEPTED`, `REVOKED` and `EXPIRED` ones are returned with their `status`, because the record is the trail of who was granted access to the account's fiscal data and revoking it does not erase it.","operationId":"getAccountInvitation","x-required-scopes":["members:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:read"]}],"responses":{"200":{"description":"Invitation retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/InvitationSummary"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagement"},"404":{"$ref":"#/components/responses/InvitationNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Invitations"],"summary":"Revoke an invitation","description":"Revokes a `PENDING` invitation, so its acceptance link stops working.\n\n- **Already resolved:** an `ACCEPTED`, `REVOKED` or `EXPIRED` invitation cannot be\n  revoked, and answers `404` without disclosing which of the three it is.\n- **History:** revoking does not remove the invitation from the list.\n","operationId":"deleteAccountInvitation","x-required-scopes":["members:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["members:write"]}],"responses":{"204":{"description":"Invitation revoked successfully"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenMemberManagementLiveCredentialRequired"},"404":{"description":"`PENDING_INVITATION_NOT_FOUND` — no revocable invitation with that id exists in the account. Returned both when the invitation does not exist and when it exists but is not in PENDING state (state is not disclosed). It is a different code from the `INVITATION_NOT_FOUND` of `GET /v1/accounts/{account_id}/invitations/{invitation_id}`, which reads invitations in any state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PENDING_INVITATION_NOT_FOUND","message":"No pending invitation with that id in this account."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts":{"get":{"tags":["Provisioning"],"summary":"List the accounts you provisioned","description":"Returns the accounts you provisioned, newest first. Each carries its lifecycle `status`\n(`PROVISIONED` → `CLAIMED` → `ACTIVE`), the `access_level` you hold over it and the state\nof its claim link.\n\n- **`status`:** narrows the list to one lifecycle stage.\n- **`external_ref`:** looks an account up by the reference you assigned when provisioning\n  it; returns the 0..1 matching accounts.\n\n**Cursor pagination.** This collection pages by `cursor`/`next_cursor` instead of by\n`page`, so it carries no `pagination` block. That is a documented variant of pagination,\nnot a different envelope: the collection still travels under a named key inside `data`.\nKeep asking with the `next_cursor` of the previous response until it comes back `null`.\n","operationId":"listAccounts","x-pagination-style":"cursor","x-required-scopes":["accounts:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:read"]}],"parameters":[{"name":"status","in":"query","required":false,"description":"Keeps only the accounts at this lifecycle stage. Omitted, every stage is listed.","schema":{"$ref":"#/components/schemas/ProvisioningStatus"}},{"name":"external_ref","in":"query","required":false,"description":"Your own id for the account; returns the 0..1 matching accounts.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of accounts to return per page (1–200). Defaults to 50.","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's `next_cursor`.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of provisioned accounts.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["accounts"],"properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/ManagedAccountSummary"}},"next_cursor":{"type":"string","nullable":true,"description":"Pass as `cursor` for the next page; `null` when there are no more results."}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"The query was understood and refused. `error.code` is `PROVISIONING_INVALID_CURSOR`\nwhen `cursor` is not a `next_cursor` this operation returned; a `limit` out of range\nanswers `VALIDATION_ERROR` naming it in `error.details`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PROVISIONING_INVALID_CURSOR","message":"The pagination cursor is not valid: use the next_cursor from the previous page."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["Accounts"],"summary":"Provision an account","description":"Provisions a new account on BeeL and, when it is born with a holder, returns a single-use\n`claim_token` to deliver so they can set a password and take ownership.\n\n- **`email`:** send it to create the account with a holder. Omit it and the account is\n  created with no person at all, no `person_id` and no `claim_token`; a holder can be\n  added later with `POST /v1/accounts/{account_id}/claim-tokens`.\n- **`tax_profile`:** send it and the account comes back ready to invoice, with its NIF,\n  default invoice series and VeriFactu configuration set up. Omit it and the account's\n  company is created without a NIF until its holder registers one. Either way its\n  `company_id` is in the response.\n- **`access_level`:** the access you retain over the account. Defaults to `NONE`;\n  `OPERATE` requires a `tax_profile`.\n- **`external_ref`:** the idempotency key. Resending the same one returns the existing\n  account rather than creating a second.\n- **Entitlement:** requires `manage_accounts`.\n\n## Reactivation\n\nIf you previously ended your management of this account\n(`DELETE /v1/accounts/{account_id}/management`) and its holder has not claimed it yet,\nprovisioning the same email reactivates that account instead of creating a new one. The\nsame account, holder, NIFs and invoices come back under your management, with the\n`external_ref` and `access_level` of this request, and it counts towards your billable\nusage again. Once the holder has claimed the account it is theirs, and only they can\ngrant you access again.\n","operationId":"provisionAccount","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProvisionAccountRequest"},"examples":{"provision_account_with_company":{"$ref":"#/components/examples/provision-with-company"},"provision_account_minimal":{"$ref":"#/components/examples/provision-minimal"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Account provisioned successfully. **Business idempotency** by `external_ref`: resending the same `external_ref` returns **201 with the same account id** (not 409). While the account is still unclaimed, a claim link that is still valid is **never revoked**: the response comes back with `claim_token` and `claim_url` `null` and `claim_link_already_issued: true`, and no email is re-sent. Only when no valid link remains (expired, or none was ever issued) is a fresh `claim_token` issued. To replace a valid link on purpose, use `POST /v1/accounts/{account_id}/claim-tokens`. For **exact response idempotency** (same body, same token on retries) send an `Idempotency-Key` header — the response is cached for 24h.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProvisionAccountResult"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Conflict. Returned when the email is already used by an existing BeeL. account that this request can neither reuse idempotently (same `external_ref`) nor reactivate. Two distinct cases:\n\n- `PROVISIONING_ACCOUNT_CLAIMED` — you did manage this account and ended it, but its holder has already claimed it. It is theirs now: ask them to grant you access from their account. Provisioning cannot take it back.\n- `PROVISIONING_EMAIL_ALREADY_REGISTERED` — the email belongs to an account you never managed, one someone else manages, or one whose management someone else ended. Use a different email or contact support.\n\n- `PROVISIONING_EXTERNAL_REF_CONFLICT` — two provisioning requests with the same `external_ref` ran at the same time and the other one created the account first. Retry the same request: it now finds that account and returns it with `201`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error. Specific to this operation:\n\n- `PROVISIONING_TAX_PROFILE_REQUIRED` — `access_level` is `OPERATE` and there is no\n  `tax_profile`: invoicing on the account's behalf needs its NIF, so it is refused here rather\n  than at the first invoice.\n- `PROVISIONING_EMAIL_REQUIRED` — `send_email` is `true` and there is no `email` to send the\n  claim link to.\n- `DISPLAY_NAME_BLANK` — `display_name` is made only of whitespace: it passes the length\n  check but leaves the account without a name to show.\n\nAny other field failure answers `VALIDATION_ERROR`, with the offending field in `error.details`.\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PROVISIONING_TAX_PROFILE_REQUIRED","message":"With access_level OPERATE you must send tax_profile: invoicing on the account's behalf needs its NIF."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"`EXTERNAL_SERVICE_ERROR` — the AEAT census could not be reached to validate the NIF. Only happens when the request carries a `tax_profile`; the account was not created. Nothing was\nstored. It is transient; repeat\nthe same request later (with the same `Idempotency-Key` if you sent one). No\n`Retry-After` header is sent.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"EXTERNAL_SERVICE_ERROR","message":"A technical error occurred. Please try again later.","details":{}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"type":"https://docs.beel.es/errors/EXTERNAL_SERVICE_ERROR","title":"EXTERNAL_SERVICE_ERROR","detail":"A technical error occurred. Please try again later.","instance":"/api/v1/accounts"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}":{"get":{"tags":["Provisioning"],"summary":"Retrieve a provisioned account","description":"Returns one account you provisioned, with the same shape the list returns: its lifecycle `status`, the `access_level` you hold, the state of its claim link and its `company_id` when the account holds exactly one company.","operationId":"getAccount","x-required-scopes":["accounts:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:read"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Identifier (UUID) of an account you provisioned. An account you did not provision answers exactly like one that does not exist, so its existence is never disclosed.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The provisioned account.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ManagedAccountSummary"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Every cause listed under the plain `403`, plus `PROVISIONING_ACCOUNT_NOT_ACCESSIBLE`: the account was not provisioned by you, its management ended, or it does not exist. The three are answered identically, so existence is never disclosed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PROVISIONING_ACCOUNT_NOT_ACCESSIBLE","message":"That account is not managed by your organisation."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/access-level":{"patch":{"tags":["Accounts"],"summary":"Change your access over a managed account","description":"Updates the `access_level` you keep over an account you provisioned.\n\n- **Raising it:** only possible while the account is unclaimed. Once its holder has taken\n  ownership you may keep or lower your access, but only they can raise it.\n- **Billing:** the level never affects it — you pay for the account's subscription at any\n  level.\n- **`OPERATE`:** issuing invoices on the holder's behalf additionally requires a signed\n  fiscal representation from them.\n- **Entitlement:** requires `manage_accounts`.\n","operationId":"changeManagedAccountAccessLevel","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Identifier (UUID) of an account you manage. An account you do not manage answers exactly like one that does not exist, so its existence is never disclosed.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeAccessLevelRequest"},"examples":{"change_access_level_view":{"$ref":"#/components/examples/access-level-view"}}}}},"responses":{"204":{"description":"Access updated."},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Every cause listed under the plain `403`, plus `LIVE_CREDENTIAL_REQUIRED` when the call is made with a test API key (`beel_sk_test_…`), and `MANAGED_SCOPE_ELEVATION_REQUIRES_OWNER` when the request raises your access over an account its holder has already claimed: from then on only they can raise it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"MANAGED_SCOPE_ELEVATION_REQUIRES_OWNER","message":"Only the account holder can raise management access over their account once it has been claimed"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/management":{"delete":{"tags":["Accounts"],"summary":"End your management of an account","description":"Ends the management relationship over an account you provisioned: you lose access to it,\nand its NIFs stop counting towards your billable usage from the next billing cycle.\n\n- **The holder:** keeps the account, its NIFs and its invoices, and becomes responsible\n  for their own subscription. Nothing is deleted or anonymised.\n- **Reversible:** only while the account stays unclaimed. Provisioning the same email\n  again reactivates it (see `POST /v1/accounts`), and only the manager who ended the\n  relationship can do so. Once the holder claims the account it is theirs, and getting the\n  management back needs their consent, not just their email address.\n- **Entitlement:** requires `manage_accounts`.\n","operationId":"endAccountManagement","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Identifier (UUID) of an account you manage. An account you do not manage answers exactly like one that does not exist, so its existence is never disclosed.","schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Management ended. The account remains active under its holder."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/ForbiddenLiveCredentialRequired"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/claim-tokens":{"post":{"tags":["Accounts"],"summary":"Issue a claim token","description":"Issues a single-use `claim_token`, and the `claim_url` built from it, so the account's\nholder can set a password and take ownership.\n\n- **`email`:** send it when the account has no holder yet — the person is created by this\n  call. Omit the body to re-issue the token for the holder the account already has. An\n  `email` that differs from the existing holder's is rejected rather than replacing them.\n- **Lifetime:** tokens last 30 days, and only the last one issued is live. Issuing again\n  invalidates the previous token, so the old link stops working the moment you ask for a\n  new one.\n- **Not an invitation:** this hands the account itself over to its holder. To add a\n  further person to an account that already has one, invite them with\n  `POST /v1/accounts/{account_id}/invitations`.\n- **Entitlement:** requires `manage_accounts`.\n","operationId":"createAccountClaimToken","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Identifier (UUID) of an account you provisioned. An account you did not provision answers exactly like one that does not exist, so its existence is never disclosed.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClaimTokenRequest"},"examples":{"create_claim_token_new_holder":{"$ref":"#/components/examples/claim-token-new-holder"}}}}},"responses":{"201":{"headers":{"Location":{"description":"The claim link itself (`claim_url`): the created resource is the single-use claim link, which has no canonical GET — it is shown once.","schema":{"type":"string","format":"uri"}}},"description":"Token issued. Any previously issued token for this account is now invalid. Send an `Idempotency-Key` header to get the same token back on a retry instead of a fresh one.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/ClaimTokenResult"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`PROVISIONING_ACCOUNT_NOT_ACCESSIBLE` — the account was not provisioned by you, or it does not exist. Unlike `GET /v1/accounts/{account_id}`, which answers this case with `403`, this operation answers `404`; both carry the same `error.code`, so branch on it rather than on the status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PROVISIONING_ACCOUNT_NOT_ACCESSIBLE","message":"That account is not managed by your organisation."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"409":{"description":"Conflict. `PROVISIONING_ACCOUNT_CLAIMED` — the holder already took ownership, so there is nothing left to claim. `PROVISIONING_EMAIL_ALREADY_REGISTERED` — the `email` you sent belongs to another BeeL. account. `CLAIM_TOKEN_HOLDER_MISMATCH` — the account already has a holder and the `email` you sent is a different one; omit it to re-issue for the holder it already has.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation error. Specific to this operation:\n\n- `CLAIM_TOKEN_EMAIL_REQUIRED` — the account has no holder yet and the request carries no\n  `email`: the holder is created from it, so there is nobody to issue the token to without one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"CLAIM_TOKEN_EMAIL_REQUIRED","message":"The account has no holder yet: send the email of the person who will claim it."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/usage":{"get":{"tags":["Accounts"],"summary":"Get your provisioning usage","description":"Returns how many accounts you have provisioned and the billable count that follows from\nthem — the figure behind your offline B2B invoice.\n\n- **Billable unit:** the provisioned account, not the real NIF. Every account you\n  provision counts as one, empty and unclaimed ones included.\n- **`account_id`:** your own account. Usage is a property of the provisioner, not of each\n  provisioned account, so any other id returns `404`.\n- **Entitlement:** requires `manage_accounts`.\n","operationId":"getAccountUsage","x-required-scopes":["accounts:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:read"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account id.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Provisioning usage of the caller","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ProvisioningUsage"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`PROVISIONING_ACCOUNT_NOT_ACCESSIBLE` — the `account_id` is not your own account. Unlike `GET /v1/accounts/{account_id}`, which answers this case with `403`, this operation answers `404`; both carry the same `error.code`, so branch on it rather than on the status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PROVISIONING_ACCOUNT_NOT_ACCESSIBLE","message":"That account is not managed by your organisation."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/imports":{"post":{"operationId":"createAccountImport","x-no-location":"An import provisions many accounts — or none, when every row is blocked — so there is no single created resource to point at. Each row carries its own id in `account.account_id`.","tags":["AccountImports"],"summary":"Import managed accounts from a file","description":"Provisions the managed accounts described in an uploaded file, switches each one on in\nLive, and leaves them ready to invoice. It is the same act as calling `POST /v1/accounts`\nonce per row and then `POST /v1/companies/{company_id}/activations`, with the bookkeeping\ndone for you.\n\n## Idempotency and re-runs\n\n- **Not atomic:** each row is processed and reported independently, and a row that fails\n  leaves the rows already provisioned in place. `statistics.accounts_created` is how many\n  accounts this call actually created.\n- **Declarative and re-runnable:** each pass applies only what is missing — an\n  `external_ref` you already provisioned is reconciled, not duplicated, and so are its\n  series and its customers. That is the recovery path for anything that went wrong: fix the\n  cause and upload the same file again; there is no resume and no partial state to clean\n  up.\n- **`Idempotency-Key`:** required, but the real guarantee is in the data. Rows are\n  idempotent by `external_ref`, so the same file uploaded twice creates nothing twice even\n  under a different key.\n- **Dry run:** to see what this would do without writing anything, use\n  `POST /v1/accounts/imports/preview`, a separate operation with no effects at all — the\n  import is never governed by a boolean flag.\n\n## Files and limits\n\n- **`accounts_file`:** describes the accounts, one per row.\n- **`customers_file`:** optional, and holds a list of customers applied to **every** account\n  of the import, new and pre-existing alike, so a new managed account is born knowing all\n  the customers and a new customer reaches all the accounts on the next pass. It is the\n  same CSV that `GET /v1/templates/customer-import` describes, and it is idempotent by tax\n  id.\n- **`options.apply_customers_to_own_company`:** lands those customers on your own company\n  as well — the one in focus, never one chosen for you. That outcome comes back apart, in\n  `own_company_customers`, and stays out of `statistics.customers_created`.\n- **Limits:** 5 MB per file, 100 rows in the accounts file and 1,000 in the customers file.\n  A larger population is imported in passes, which costs nothing because the file is\n  declarative.\n\n## Live activation\n\nLive activation is part of the act: every row is weighed against the same verdict the\naccount state publishes, and only rows entitled to Live are executed; the rest come back\n`BLOCKED` with the reason. The import never opens a checkout, so it never charges you by\nsurprise: settle your billing once and re-upload.\n\nIf your agreement caps the NIFs you can have in Live, each row switched on takes a place, in\nfile order; the rows that no longer fit come back `BLOCKED` with `NIF_LIMIT_REACHED`, the\nsame code `POST /v1/companies/{company_id}/activations` answers with.\n\n## Claim tokens\n\n`account.claim_token` and `account.claim_url`: each newly provisioned row carries them\nin this response and nowhere else, so persist them before discarding it. A lost token is\nre-issued with `POST /v1/accounts/{account_id}/claim-tokens`.\n\nRe-uploading the file never breaks the links you already handed out: a row whose account is\nstill unclaimed and holds a valid claim link comes back with `claim_token` and `claim_url`\n`null` and `account.claim_link_already_issued: true`. Only an account with no valid link left\n(expired, or never issued) gets a fresh one.\n","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"parameters":[{"$ref":"#/components/parameters/RequiredIdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AccountImportUpload"},"encoding":{"options":{"contentType":"application/json"}}}}},"responses":{"201":{"description":"The import ran, in the same format as the preview. The status is fixed: it reports that the import ran, not that any particular row succeeded. `metadata.is_dry_run` is `false`, and the write counters in `statistics` are what actually reached the database — they can all be `0` when every row was already there or blocked.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AccountImportResult"}}}]}}}},"400":{"description":"The file could not be read: `EMPTY_FILE`, `INVALID_FORMAT`, `ENCODING_ERROR`, `PARSING_ERROR`, `HEADER_ROW_NOT_FOUND` (no header row in its first 20 lines; the message names the file, the columns it looked for and the closest row it saw), `DUPLICATE_HEADER` (a column appears twice, so there is no telling which one holds the value) or `NO_DATA_ROWS` (a header row and nothing under it). Nothing was written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"`PAYMENT_REQUIRED` — your own billing is unpaid (payment retries are exhausted), so nothing can be provisioned on your behalf until the outstanding invoice is settled. Decided before the file is read: no row is reported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`FEATURE_NOT_AVAILABLE` — your account does not hold the managed-accounts capability. A credential without the `accounts:write` scope answers the same way.\n\nWith `options.apply_customers_to_own_company` there are two more, both decided before the files are read: `ACTIVE_COMPANY_REQUIRED`, when your account holds several NIFs and none is in focus — the option writes on your own company and this endpoint never picks one for you — and `INSUFFICIENT_SCOPE`, when the API key lacks `customers:write`.\n\n`MANAGED_ACCESS_INSUFFICIENT` — the access you hold over an existing managed account was lowered below what the import needs while the request was already running. Nothing is written for that account; retry once the access is back to `OPERATE`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`IDEMPOTENCY_KEY_MISMATCH` — this key was already used for a different request. Use a new key, or resend the original request to replay its cached response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"`TOO_MANY_RECORDS` — a file carries more rows than its limit. The whole file is rejected and no row is reported: an import of this weight is answered synchronously, so the cap is what keeps the request from timing out half-done.\n\n`MISSING_REQUIRED_FIELD` naming `customers_file` — `options.apply_customers_to_own_company` was sent without the customers file it applies. Ignoring the option instead would answer `201` to a request that did none of what it asked for.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/imports/preview":{"post":{"operationId":"previewAccountImport","tags":["AccountImports"],"summary":"Preview an import of managed accounts","description":"Reads the same files as `POST /v1/accounts/imports` and answers the same shape without\nwriting anything: no account is provisioned, no NIF is switched on, no series and no\ncustomer are created, and nothing is billed.\n\n- **Result shape:** `metadata.is_dry_run` is `true`, every write counter in `statistics` is\n  `0`, and `statistics.importable` is what a real import would create.\n- **Per row:** it resolves the row's own data — including the repairs a spreadsheet export\n  needs, which `accounts_file` describes — whether the tax id is in the AEAT register,\n  whether the `external_ref` is already an account of yours, and the Live activation\n  verdict that decides whether the import would execute the row at all.\n- **`statistics.live_activations_pending`:** read it before importing. Every NIF switched\n  on in Live adds an item to your subscription, and this is the only place to see the total\n  before it is charged.\n- **The customers file** is checked once for the whole import: whether a customer is new to\n  a given account depends on the account, and that only shows up when the import runs.\n","x-required-scopes":["accounts:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["accounts:write"]}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AccountImportUpload"},"encoding":{"options":{"contentType":"application/json"}}}}},"responses":{"200":{"description":"Preview completed. Nothing was written and nothing was billed.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AccountImportResult"}}}]}}}},"400":{"description":"The file could not be read: `EMPTY_FILE`, `INVALID_FORMAT`, `ENCODING_ERROR`, `PARSING_ERROR`, `HEADER_ROW_NOT_FOUND` (no header row in its first 20 lines; the message names the file, the columns it looked for and the closest row it saw), `DUPLICATE_HEADER` (a column appears twice, so there is no telling which one holds the value) or `NO_DATA_ROWS` (a header row and nothing under it). Nothing was written.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"`PAYMENT_REQUIRED` — your own billing is unpaid (payment retries are exhausted), so nothing can be provisioned on your behalf until the outstanding invoice is settled. Decided before the file is read: no row is reported.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`FEATURE_NOT_AVAILABLE` — your account does not hold the managed-accounts capability. A credential without the `accounts:write` scope answers the same way.\n\nWith `options.apply_customers_to_own_company` there are two more, both decided before the files are read: `ACTIVE_COMPANY_REQUIRED`, when your account holds several NIFs and none is in focus — the option writes on your own company and this endpoint never picks one for you — and `INSUFFICIENT_SCOPE`, when the API key lacks `customers:write`.\n\n`MANAGED_ACCESS_INSUFFICIENT` — the access you hold over an existing managed account was lowered below what the import needs while the request was already running. Nothing is written for that account; retry once the access is back to `OPERATE`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"`TOO_MANY_RECORDS` — a file carries more rows than its limit. The whole file is rejected and no row is reported: an import of this weight is answered synchronously, so the cap is what keeps the request from timing out half-done.\n\n`MISSING_REQUIRED_FIELD` naming `customers_file` — `options.apply_customers_to_own_company` was sent without the customers file it applies. Ignoring the option instead would answer `201` to a request that did none of what it asked for.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/fiscal-summary":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyFiscalSummary","tags":["CompanyFiscalSummary"],"summary":"Get the fiscal summary of a company","description":"Returns the VAT and IRPF summary of the invoices issued under this company over the requested period, together with the annual IRPF projection and its progressive bracket breakdown.\n`start_date` and `end_date` go together: send both, or neither. Omitting both defaults to the current month; sending only one answers `400`, because a period you did not ask for is worse than an error. The range may not exceed 365 days, and every fault names itself in `details.reason`.","x-required-scopes":["invoices:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["invoices:read"]}],"parameters":[{"name":"start_date","in":"query","required":false,"description":"Period start date (inclusive), as `YYYY-MM-DD`. Goes together with `end_date`:\nsupply both or neither. Omitting both defaults to the current month; supplying\nonly one is rejected with `400` (`PERIOD_INCOMPLETE`).\n","schema":{"type":"string","format":"date"},"example":"2025-01-01"},{"name":"end_date","in":"query","required":false,"description":"Period end date (inclusive), as `YYYY-MM-DD`. Goes together with `start_date`:\nsupply both or neither. Omitting both defaults to the current month; supplying\nonly one is rejected with `400` (`PERIOD_INCOMPLETE`).\n","schema":{"type":"string","format":"date"},"example":"2025-03-31"}],"responses":{"200":{"description":"The fiscal summary of the company for the requested period.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/FiscalSummaryResponse"}}}]}}}},"400":{"description":"The request target is malformed: a date that is not `YYYY-MM-DD`, or a range that is\nnot a period. A range fault names itself in `details.reason`:\n- `PERIOD_INVERTED` — `start_date` is after `end_date`\n- `PERIOD_TOO_LONG` — the range exceeds `details.max_days` (365)\n- `PERIOD_INCOMPLETE` — only one of `start_date` / `end_date` was supplied\n\n`details` also echoes whichever of `start_date` and `end_date` was received. These are query\nparameters, so the fault is in the request target and the status is `400`, not `422`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/verifactu":{"get":{"tags":["ConfigurationVeriFactu"],"summary":"Get VeriFactu configuration","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/verifactu-configuration`, which\nbehaves identically.\n\nReturns the VeriFactu configuration in force for the company in focus.\n\n- **Writable state:** `enabled` says whether this tax ID is under the VeriFactu regime in\n  this environment: while it is on, every invoice of the NIF is registered with the AEAT.\n- **Resolved state:** `status`, `signed`, `activated` and `nif_status` are read-only and\n  resolved server-side.\n\n**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.\n","operationId":"getVeriFactuConfiguration","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/verifactu-configuration"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Configuration retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VeriFactuConfiguration"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["ConfigurationVeriFactu"],"summary":"Update VeriFactu configuration","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/verifactu-configuration`, which\nbehaves identically.\n\nUpdates the VeriFactu configuration of the company in focus.\n\n- **Writable fields:** only `enabled`. The rest of the returned\n  configuration (`status`, `signed`, `activated`, `nif_status`) is resolved server-side.\n- **Full replacement:** `enabled` is required — this PUT replaces the whole state, it is\n  not a partial merge, so omitting it is a client error and not a silent `false`.\n- **Turning it on registers the NIF for VeriFactu submission in the same call**,\n  atomically: if the registration is refused nothing is persisted and the response carries\n  the reason. In Live it requires a signed and validated AEAT representation first, or\n  `422 VERIFACTU_REPRESENTATION_REQUIRED`.\n- **Sandbox is always on:** `enabled: false` there answers `422`\n  `VERIFACTU_ALWAYS_ON_IN_SANDBOX`.\n\n## Turning it off\n\nSetting `enabled` to false stops sending invoices to the AEAT and starts deregistering\nthe NIF from VeriFactu submission. It does **not** deactivate the\nNIF: the activation is a fact of its own for the (company, environment) pair, so issuing\ncarries on and `issuing-readiness` stays `ready`. Releasing the NIF is always\n`DELETE /v1/companies/{company_id}/activations`, with its own guarantees.\n\n**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.\n","operationId":"updateVeriFactuConfiguration","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/verifactu-configuration"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVeriFactuConfigurationRequest"}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VeriFactuConfiguration"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/tax-types":{"get":{"tags":["ConfigurationTax"],"summary":"Get complete tax types catalog","deprecated":true,"x-deprecation":"nif-migration","description":"**Deprecated.** Use `GET /v1/tax-types`, which returns exactly the same catalog. It is\nreference data shared by every account, not the settings of a NIF, so it does not belong\nunder `/v1/configuration`.\n\nReturns the catalog of Spanish tax regimes with their valid percentages and VeriFactu\nregime codes.\n\n- **`tax_regimes`:** the tax types and the percentages and regime keys each one admits.\n- **`irpf_types`:** the available IRPF withholding rates.\n- **`equivalence_surcharges`:** the surcharge that corresponds to each VAT percentage.\n- **`exemption_reasons`:** the accepted exemption reasons.\n\n**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.\n","operationId":"getTaxTypes","x-successor":{"method":"GET","path":"/v1/tax-types"},"x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Catalog retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxTypesCatalogResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/invoice-customization-options":{"get":{"tags":["ConfigurationPreferences"],"summary":"Get invoice customization options","deprecated":true,"x-deprecation":"nif-migration","description":"**Deprecated.** Use `GET /v1/invoice-customization-options`, which returns exactly the\nsame body. The catalogue is the same for every credential, so it does not belong under\n`/v1/configuration`.\n\nReturns, in `template_types`, the catalogue of PDF templates a NIF can be rendered with.\n\n- **Fields:** each entry carries the `code` to send as `template_type`, plus a `name` and a\n  short `description` translated into the language of the user the credential belongs to.\n- **Removed fields:** `suggested_colors`, `features` and `preview_url` are no longer\n  returned; the invoice customization accepts any 6-digit hexadecimal colour, so pick your\n  own.\n\n**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.\n","operationId":"getInvoiceCustomizationOptions","x-successor":{"method":"GET","path":"/v1/invoice-customization-options"},"x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Customization options retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceCustomizationOptionsResponse"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/taxes":{"get":{"tags":["ConfigurationTax"],"summary":"Get user tax configuration","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/tax-configuration`, which behaves\nidentically.\n\nRetrieves the tax configuration of the company in focus: the default main tax\n(`IVA`, `IGIC`, `IPSI` or `OTHER`) with its percentage and regime key, the default\nexemption reason, the IRPF and equivalence surcharge settings, and the default payment\nmethod and payment term.\n\n**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.\n","operationId":"getTaxConfiguration","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/tax-configuration"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Configuration retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TaxConfiguration"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"tags":["ConfigurationTax"],"summary":"Update tax configuration","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/tax-configuration`, which behaves\nidentically.\n\nUpdates the tax configuration of the company in focus. Fields you omit keep their\ncurrent value.\n\n- **Regime coherence:** the main tax and its VeriFactu regime key must match. Regime key\n  `18` (equivalence surcharge) only exists for `IVA`, so pairing it with `IGIC`, `IPSI` or\n  `OTHER` answers `422` `INVALID_REGIME_KEY_FOR_TAX_TYPE`, with `details` naming the\n  rejected key, the tax type and the keys that type admits.\n- **Surcharge:** applying the surcharge without regime key `18` answers `422`\n  `RECARGO_REQUIRES_REGIME_RE`.\n- **Exemption reason:** `default_exemption_reason` travels with `default_main_tax` —\n  sending the tax without a reason clears the stored one, and sending only the reason\n  applies it to the tax already stored.\n\n**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.\n","operationId":"updateTaxConfiguration","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/tax-configuration"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaxConfigurationRequest"}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TaxConfiguration"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/series":{"get":{"tags":["InvoiceSeries"],"summary":"List invoice series","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/series`, which behaves identically.\n\nRetrieves the invoice series of the company in focus. The listing is always scoped\nto one company; it never spans several.\n\n- **Filters:** `active` restricts to active or inactive series — omit it and you get all of\n  them. `document_type` returns the series of that type, the only ones that can number it.\n- **Pagination (opt-in):** send `page` and/or `limit` to receive a single page plus a\n  `data.pagination` block with the totals. Omit both and the response carries the full list\n  in `data.series` and no `pagination` block.\n\n**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.\n","operationId":"listSeries","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/series"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:read"]}],"parameters":[{"name":"active","in":"query","description":"Filters by activity: `true` returns only active series, `false` only inactive ones.\nOmit it and you get **all** the series, active and inactive.\n","schema":{"type":"boolean"}},{"name":"document_type","in":"query","description":"Filter by document type","schema":{"$ref":"#/components/schemas/DocumentType"}},{"name":"page","in":"query","required":false,"description":"Page number (starts at 1). Omit for the full, unpaginated list.","schema":{"type":"integer","minimum":1},"example":1},{"name":"limit","in":"query","required":false,"description":"Items per page. Omit for the full, unpaginated list.","schema":{"type":"integer","minimum":1,"maximum":100},"example":20}],"responses":{"200":{"description":"Series retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["series"],"properties":{"series":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSeries"}},"pagination":{"allOf":[{"$ref":"#/components/schemas/Pagination"}],"description":"Totals of the requested page. Present ONLY when the request sent\n`page` and/or `limit`; omitted for the historical full list.\n"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"tags":["InvoiceSeries"],"summary":"Create invoice series","description":"**Deprecated.** Use `POST /v1/companies/{company_id}/series`, which behaves identically.\n\nCreates a new invoice series for the company in focus.\n\n- **Code:** must be unique within the company; a code already taken answers `409`.\n- **Numbering:** `format` must contain `{NUM}` or `{NUM:X}` and only accepts uppercase\n  tokens. `counter_reset` defaults to `ANNUAL`, so a format with no year token has to be\n  sent with `counter_reset: NEVER`.\n- **Default series:** the first series created for a document type is marked as default\n  even if you send `default_series: false`.\n\n**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.\n","operationId":"createSeries","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/series"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSeriesRequest"}}}},"responses":{"201":{"description":"Series created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The series clashes with another series of the company: `SERIES_CODE_DUPLICATED` (the\ncode is taken) or `SERIES_FORMAT_OVERLAPS` (it could print numbers the other series\nprints).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The series is rejected. `error.code` is `VALIDATION_ERROR` when the body parses but a value\nis not acceptable: `details` is a flat map from the offending property's name (in the\ncontract's `snake_case`) to a message describing what is wrong with it, or follows\n`FieldDeserializationError` when the value is outside an enum's vocabulary. Otherwise it is\none of the series rules, among them:\n- `SERIES_MONTHLY_REQUIRES_MONTH_AND_YEAR` / `SERIES_ANNUAL_REQUIRES_YEAR` — the format\n  cannot tell the counter reset periods apart.\n- `SERIES_INITIAL_NUMBER_OUT_OF_RANGE` — the initial number is out of range.\n- `SERIES_FORMAT_NUMBER_TOO_LONG` — the longest number the series can generate (the\n  format with the code, the year, the month and the counter substituted) exceeds the 60\n  characters the AEAT accepts for the invoice number. The counter counts as at least 9\n  digits, with or without padding: `{NUM:X}` is a minimum width and the number keeps\n  growing past it.\n- `SERIES_FORMAT_INVALID_CHARACTERS` — that number would contain a character the AEAT does\n  not accept: only printable ASCII (32-126) is allowed, except `\"`, `'`, `<`, `>` and `=`.\n- `SERIES_TYPE_CHANGE_LEAVES_TYPE_UNCOVERED` — changing `document_type` of the default\n  series would leave its current document type, which still has other active series,\n  without a default. Make one of those the default first.\n- `SERIES_COMPANY_DELETED` — the company was deleted while the request was running: its\n  series keep their numbering but can no longer be activated or made the default.\n\nOther `error.code` values with this status:\n\n- `SERIES_FORMAT_INVALID_PADDING` — the padding in `{NUM:X}` must be a whole number from 1 to 20 (e.g. `{NUM:4}` → `0001`).\n- `SERIES_FORMAT_NUM_REQUIRED` — the series `format` has no `{NUM}` or `{NUM:X}`, so it cannot number invoices. Add the counter.\n- `SERIES_FORMAT_UNRECOGNIZED_VARS` — the `format` uses a variable BeeL. does not know. Allowed: `{CODIGO}`, `{YYYY}`, `{YY}`, `{MM}`, `{NUM}`, `{NUM:X}`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"SERIES_FORMAT_NUMBER_TOO_LONG","message":"The number this series generates can reach 64 characters and the AEAT accepts at most 60 (series and number together). Shorten the code or the fixed text of the format"},"meta":{"timestamp":"2026-09-24T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/series/defaults":{"post":{"tags":["InvoiceSeries"],"summary":"Create default invoice series","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/series/defaults`, which behaves\nidentically. Ensuring a set of defaults is idempotent, so the canonical form is a `PUT`.\n\nIdempotently ensures the company in focus has a default invoice series for each\nrelevant `DocumentType` (`STANDARD`, `SIMPLIFIED`, `CORRECTIVE`) in the current\nenvironment.\n\n- **Already there:** a document type that already has a default keeps it, and it is\n  returned unchanged.\n- **Missing:** a new series is created with code `F`, `S` or `R` and format\n  `{CODIGO}-{YYYY}-{NUM:4}`, active and marked as default.\n- **Code taken:** if that code is already in use by a manually created series, the document\n  type is skipped and omitted from the response.\n\n**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.\n","operationId":"createDefaultSeries","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/series/defaults"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Default series ensured","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["series"],"properties":{"series":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSeries"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/series/defaults-status":{"get":{"tags":["InvoiceSeries"],"summary":"Get default series status per document type","description":"**Deprecated.** Use `GET /v1/companies/{company_id}/series/defaults`, which behaves\nidentically.\n\nReports, for each `DocumentType` used by automatic invoicing, whether there is a default\ninvoice series and which one: `exists`, plus the `series_id` when there is one.\n\n- **Why check it:** a document type with no default series makes an incoming charge be\n  skipped with `failure.payment.skip.missing_default_series` instead of invoiced.\n- **Environment:** test or production is resolved automatically from your request context;\n  no input needed.\n\n**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.\n","operationId":"getDefaultSeriesStatus","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/series/defaults"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:read"]}],"responses":{"200":{"description":"Default series status retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["defaults"],"properties":{"defaults":{"type":"array","items":{"$ref":"#/components/schemas/DocumentTypeDefaultStatus"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/series/{series_id}":{"put":{"tags":["InvoiceSeries"],"summary":"Update invoice series","description":"**Deprecated.** The canonical form has a single update verb,\n`PATCH /v1/companies/{company_id}/series/{series_id}`. The same body produces the same\nresult there — this route already merges field by field, leaving absent fields untouched —\nwith one difference: an explicit `description: null`, which this route ignores, clears the\ndescription under `PATCH`.\n\nUpdates an existing invoice series with the body you send; absent fields keep their value.\n\n- **Numbering fields:** `code`, `format`, `counter_reset` and `initial_number` are rejected\n  once the series has issued invoices (`numbering_locked` is `true`). `name`,\n  `description`, `active`, `default_series` and `document_type` can always be changed.\n- **`active`:** a default series cannot be deactivated — set another one as default first.\n- **`default_series`:** sending `false` on the series that currently is the default is\n  rejected with `DEFAULT_CANNOT_BE_UNMARKED`. Promote another series with\n  `PUT /v1/companies/{company_id}/series/{series_id}/default`, which unmarks the previous\n  one for you. An inactive series cannot be marked as default.\n\n**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.\n","operationId":"updateSeries","deprecated":true,"x-deprecation":"nif-migration","x-no-successor":"The canonical form only has PATCH. This route never replaced wholesale — it merges field by field — so the same body behaves the same there, but an explicit description null diverges (ignored here, clears the field under PATCH): not a blind drop-in. Migration guidance is in the description.","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"name":"series_id","in":"path","required":true,"description":"Series ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSeriesRequest"}}}},"responses":{"200":{"description":"Series updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"description":"The update is rejected. Error codes: `SERIES_CODE_LOCKED_HAS_INVOICES`,\n`SERIES_FORMAT_LOCKED_HAS_INVOICES`, `SERIES_RESET_LOCKED_HAS_INVOICES`,\n`SERIES_INITIAL_NUMBER_LOCKED_HAS_INVOICES`, `SERIES_DOCUMENT_TYPE_LOCKED_HAS_INVOICES`\n(the series already consumed a number), `DEFAULT_CANNOT_BE_UNMARKED`, `DEFAULT_CANNOT_DEACTIVATE`,\n`INACTIVE_CANNOT_BE_DEFAULT`, `REFERENCED_BY_PAYMENT_CONNECTION`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/SeriesValidationError"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["InvoiceSeries"],"summary":"Update invoice series partially","description":"**Deprecated.** Use `PATCH /v1/companies/{company_id}/series/{series_id}`, which behaves\nidentically.\n\nUpdates only the fields present in the body, leaving every other field of the series as it\nis.\n\n- **Clearing a field:** a field sent as `null` is cleared — only `description` supports it\n  (see `PatchSeriesRequest`).\n- **Numbering fields:** the same guard as `PUT`. `code`, `format`, `counter_reset` and\n  `initial_number` are rejected once the series has issued invoices.\n- **`default_series`:** it is not a way to clear the default. Sending `false` for the\n  series that currently *is* the default is rejected with `DEFAULT_CANNOT_BE_UNMARKED`;\n  promote another series with\n  `PUT /v1/companies/{company_id}/series/{series_id}/default` instead. Sending `false` for\n  a series that is *not* the default stays a no-op `200`.\n\n**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.\n","operationId":"patchSeries","x-successor":{"method":"PATCH","path":"/v1/companies/{company_id}/series/{series_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"name":"series_id","in":"path","required":true,"description":"Series ID","schema":{"$ref":"#/components/schemas/UUID"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchSeriesRequest"}}}},"responses":{"200":{"description":"Series updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"description":"The update is rejected. Error codes: `SERIES_CODE_LOCKED_HAS_INVOICES`,\n`SERIES_FORMAT_LOCKED_HAS_INVOICES`, `SERIES_RESET_LOCKED_HAS_INVOICES`,\n`SERIES_INITIAL_NUMBER_LOCKED_HAS_INVOICES`, `SERIES_DOCUMENT_TYPE_LOCKED_HAS_INVOICES`\n(the series already consumed a number), `DEFAULT_CANNOT_BE_UNMARKED`, `DEFAULT_CANNOT_DEACTIVATE`,\n`INACTIVE_CANNOT_BE_DEFAULT`, `REFERENCED_BY_PAYMENT_CONNECTION`,\n`REFERENCED_BY_RECURRING_INVOICE` (deactivating a series with a live —ACTIVE or\nPAUSED— recurring invoice template using it; finish it or switch it to another\nseries first).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/SeriesValidationError"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["InvoiceSeries"],"summary":"Delete series","description":"**Deprecated.** Use `DELETE /v1/companies/{company_id}/series/{series_id}`, which\nbehaves identically.\n\nSoft-deletes an invoice series. If the series is active, it is automatically deactivated\nbefore deletion.\n\n- **The code is NOT released:** it stays taken even after deletion, because it identifies\n  invoices already issued under it. Recreating a series with the same code returns\n  `409 SERIES_CODE_DUPLICATED`, so always pick a new code.\n- **Default series:** it cannot be deleted *while another active series of the same\n  document type exists* — promote that other one first. If it is the only series of its\n  type, it can be deleted and the type is left with no series: a valid state in which\n  issuing without an explicit series returns `SERIES_DEFAULT_NOT_FOUND`.\n\n**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.\n","operationId":"deleteSeries","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/series/{series_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"name":"series_id","in":"path","required":true,"description":"Series ID","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"204":{"description":"Series deleted successfully"},"400":{"description":"The series cannot be deleted. `error.code` says why:\n\n- `DEFAULT_CANNOT_DELETE` — it is the default of its document type and another\n  active series of that same type exists. Promote that one as default first.\n- `HAS_INVOICES_CANNOT_DELETE` — it has invoices associated. Remove or discard\n  those invoices first.\n- `REFERENCED_BY_PAYMENT_CONNECTION` — it is configured in a payment connection.\n  Point that connection at another series first.\n- `REFERENCED_BY_RECURRING_INVOICE` — a live (ACTIVE or PAUSED) recurring invoice\n  template uses this series. Finish it or switch it to another series first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/series/{series_id}/default":{"post":{"tags":["InvoiceSeries"],"summary":"Mark series as default","description":"**Deprecated.** Use `PUT /v1/companies/{company_id}/series/{series_id}/default`, which\nbehaves identically. Marking a series as the default is idempotent, so the canonical form\nis a `PUT`.\n\nMarks an invoice series as the default one for its document type.\n\n- **One per type:** only one series can be the default per company and document\n  type; the previous default is automatically unmarked.\n- **Must be active:** an inactive series cannot be marked as default.\n\n**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.\n","operationId":"setDefaultSeries","x-successor":{"method":"PUT","path":"/v1/companies/{company_id}/series/{series_id}/default"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"series_id","in":"path","required":true,"description":"Series ID to mark as default","schema":{"$ref":"#/components/schemas/UUID"}}],"responses":{"200":{"description":"Series marked as default successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"description":"Cannot mark as default (inactive series)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"Another request marked a different series of the same document type as the default at the\nsame time. Re-read the series and retry if the outcome is not the intended one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/configuration/language":{"put":{"tags":["ConfigurationPreferences"],"summary":"Update user preferred language","deprecated":true,"x-deprecation":"nif-migration","description":"**Deprecated — use `PATCH /v1/me` instead.** The preferred language belongs to the\nperson, not to a company's configuration.\n\nUpdates the authenticated user's preferred language.\n\n- **What it affects:** the language of the emails BeeL sends to the user and of the\n  translated labels the API returns, such as the invoice template names in\n  `GET /v1/invoice-customization-options`.\n- **Supported languages:** `es` (Spanish), `en` (English) and `ca` (Catalan).\n\n**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.\n","operationId":"updateLanguage","x-successor":{"method":"PATCH","path":"/v1/me"},"x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["language"],"properties":{"language":{"$ref":"#/components/schemas/Language"}}},"example":{"language":"ca"}}}},"responses":{"200":{"description":"Language updated successfully","headers":{"Sunset":{"description":"Date after which this route stops responding (RFC 8594). Migrate to `PATCH /v1/me`.","schema":{"type":"string","example":"Wed, 04 Nov 2026 00:00:00 GMT"}},"Deprecation":{"description":"Always `true` (RFC 8594).","schema":{"type":"string","example":"true"}},"Link":{"description":"Link to the successor route, with `rel=\"successor-version\"` (RFC 8288). The target is a URI reference, so it can be resolved against the API base URL without parsing.","schema":{"type":"string","example":"</v1/me>; rel=\"successor-version\""}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"language":{"$ref":"#/components/schemas/Language"}}}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/tax-types":{"get":{"tags":["TaxTypes"],"summary":"List the tax types allowed in Spain","description":"Returns the tax regimes and percentages that BeeL accepts on an invoice. Use it to\nvalidate a rate before sending it, or to build your own picker instead of hard-coding the\npercentages.\n\n- **Contents:** VAT (mainland), IGIC (Canary Islands), IPSI (Ceuta and Melilla), the\n  withholding (IRPF) percentages, the equivalence surcharge that corresponds to each VAT\n  rate, and the exemption reasons with the classification each one implies.\n- **Scope:** the catalogue is the same for every credential and does not depend on any\n  account or on any NIF, so the operation takes no identifier and works before the first\n  NIF exists.\n\n## VAT rates and the zero case\n\n- **VAT lists 2, 4, 5, 7.5, 10 and 21, and deliberately not 0:** under VAT (and IPSI) a 0 % is not\n  a rate but the exemption/non-subject sentinel, and on its own it says nothing. A 0 %\n  line is only valid together with an `exemption_reason`, which this same response\n  publishes under `exemption_reasons`.\n- **IGIC does list 0:** there it is the real \"Tipo Cero\" and needs no reason.\n- **Temporary VAT rates carry their period:** 5 % (2022-07-01 to 2024-09-30), and 2 % and\n  7.5 % (2024-10-01 to 2024-12-31) are listed with `valid_from` / `valid_until`. They no\n  longer apply to new operations but stay listed, because correctives and late filings for\n  those periods still need them; an invoice whose operation date falls outside the period\n  is rejected with `422 VAT_RATE_NOT_ACCEPTED_ON_DATE`. Every other rate has both `null`.\n- **Equivalence surcharges are listed per VAT ↔ surcharge pair**, each with its period.\n","operationId":"listTaxTypes","x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Catalog retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxTypesCatalogResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/invoice-customization-options":{"get":{"tags":["InvoiceCustomization"],"summary":"List the invoice PDF templates","description":"Returns the PDF templates a NIF can be rendered with. For each one, the `code` to send as\n`template_type` in `PUT /v1/companies/{company_id}/invoice-customization`, plus a name and\na short description translated into the language of the user the credential belongs to.\n\nThe accepted values are already in the `template_type` enum; what this operation adds are\nthe readable labels, so you do not have to show `MODERN_TABLE` to a person. The catalogue\nis identical for every account and every NIF, so it is not nested under one.\n\n**Closed catalogue.** This collection is fixed and bounded: it carries no `pagination`, it\ntakes no `page`/`limit`, and every response holds the whole set.\n","operationId":"listInvoiceCustomizationOptions","x-closed-catalogue":true,"x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Templates retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceCustomizationOptionsResponse"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/nif/validate":{"post":{"tags":["NIF"],"summary":"Validate a NIF against the AEAT register","description":"Checks a NIF or CIF against the AEAT register through VeriFactu and returns what the\nregister says about it. It only reads the register: it creates nothing and stores no\ncustomer.\n\n- **`status`:** distinguishes a NIF found in the register from one that is syntactically\n  correct but absent, and from a check that could not be completed because VeriFactu was\n  unavailable — in which case the NIF is validated automatically once the service is back.\n- **`valid: true`:** means different things by holder. For an individual, AEAT matched NIF\n  and name together. For a legal entity the name you sent is **not verified** at all —\n  AEAT identifies a company by its CIF alone — so it says nothing about your name.\n- **`legal_name_verified`:** tells those two cases apart.\n- **`census_status`:** says whether an identified NIF is also deregistered or revoked.\n\n## Invalid input\n\n- **Bad syntax is an answer, not an error:** it comes back `200` with `status: INVALID`, so\n  a pre-validation flow never has to tell rejections apart by status code.\n- **A missing NIF is an error:** an absent or empty `nif` answers `422` `FIELD_BLANK`, with\n  `details.field` naming it.\n","operationId":"validateNif","x-required-scopes":["nif:validate"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["nif:validate"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateNifRequest"},"example":{"nif":"B12345674"}}}},"responses":{"200":{"description":"Validation result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ValidateNifResponse"}}}]},"examples":{"valid":{"summary":"Valid NIF (individual, name cross-checked)","value":{"success":true,"data":{"valid":true,"status":"VALID","legal_name":"JUAN PEREZ GARCIA","legal_name_verified":true,"census_status":"IDENTIFIED","message":"NIF successfully validated against the AEAT census","validated_at":"2025-01-15T10:30:00Z"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}},"company":{"summary":"Valid CIF (legal entity, name NOT cross-checked)","description":"The name sent was \"PATATA FRITA SL\" and the response is still\nvalid: AEAT identified the company by its CIF alone.\n`legal_name_verified: false` says so, and `legal_name` is the\ncensus one for you to compare against.\n","value":{"success":true,"data":{"valid":true,"status":"VALID","legal_name":"MERCADONA SA","legal_name_verified":false,"census_status":"IDENTIFIED","message":"NIF successfully validated against the AEAT census","validated_at":"2025-01-15T10:30:00Z"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}},"invalid":{"summary":"Invalid NIF (not in AEAT registry)","value":{"success":true,"data":{"valid":false,"status":"INVALID","legal_name":null,"legal_name_verified":false,"census_status":"NOT_IDENTIFIED","message":"The NIF is not in the AEAT census or, for an individual, the name does not match","validated_at":null},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}},"revoked":{"summary":"Revoked NIF (in the registry, but revoked by the AEAT)","description":"A revoked NIF was annulled by the AEAT and published in the BOE: it cannot\noperate, so it is not valid on either side of an invoice. Issuing to it hurts\nboth parties, since the recipient cannot deduct anything.\n","value":{"success":true,"data":{"valid":false,"status":"INVALID","legal_name":null,"legal_name_verified":false,"census_status":"IDENTIFIED_REVOKED","message":"The NIF has been revoked by the AEAT and cannot operate","validated_at":null},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}},"deregistered":{"summary":"Deregistered NIF (in the registry, but no longer active)","description":"A deregistered NIF is not active, so it is not valid: it cannot issue invoices.\nIt can still *receive* them, though — a company closing down does not stop you\nfrom invoicing what accrued before it closed. Use `census_status` to tell this\ncase apart from a NIF that simply is not in the registry.\n","value":{"success":true,"data":{"valid":false,"status":"INVALID","legal_name":null,"legal_name_verified":false,"census_status":"IDENTIFIED_DEREGISTERED","message":"The NIF is deregistered in the AEAT census and cannot issue invoices","validated_at":null},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}},"pending":{"summary":"VeriFactu API unavailable (validation pending)","value":{"success":true,"data":{"valid":false,"status":"PENDING","legal_name":null,"legal_name_verified":false,"message":"NIF validation is pending. The system will validate it automatically when the service is available.","validated_at":null},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"123e4567-e89b-12d3-a456-426614174000"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"createCompany","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Create a company","description":"Creates a company under the account the request resolves to. The NIF is\nregistered in the name of that account's holder, never in the name of the caller.\n\n- **`activate`:** unless it is `false`, the company is switched on in\n  `aeat_environment` and its three default invoice series (ordinary, simplified,\n  corrective) are seeded there. This endpoint never switches an existing company on:\n  that is `POST /v1/companies/{company_id}/activations`.\n- **`numbering`:** decides the code, format, counter reset and starting number those\n  series are born with. Only accepted when the request activates the company.\n- **Billing:** no charge is ever started here. Creating a production NIF requires being\n  the billing subject of the account (`403` otherwise), and an account without billing is\n  rejected with `402`; no checkout is opened in either case.\n- **Duplicates:** a NIF that already exists in the account is rejected with `409`, and\n  the response carries the existing `error.details.company_id`.\n- **Addresses:** a Spanish postal code (`country_code` omitted or `ES`) must have 5 digits,\n  in `address` and in `legal_representative.address`; otherwise `422 POSTAL_CODE_INVALID_ES`\n  and nothing is created. Other countries' postal codes are free-form.\n","tags":["AccountCompanies"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCompanyRequest"},"examples":{"create_company_individual":{"$ref":"#/components/examples/create-individual"},"create_company_legal_entity":{"$ref":"#/components/examples/create-legal-entity"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Company created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse201"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Payment required before creating a production NIF. No company is created and no\ncheckout is opened. Three distinct reasons, so branch on `error.code`:\n\n* `CHECKOUT_REQUIRED` — the account has **no card on file**. Nothing is owed; billing\n  simply has not been set up. It gets set up by activating a company in Live.\n* `PAYMENT_REQUIRED` — the account has billing but is **unpaid**: payment retries are\n  exhausted. The outstanding invoice must be settled; no new checkout is opened for it.\n  A failed charge that is still being retried does not block Live.\n* `PLAN_ACTIVATION_REQUIRED` — the account is on a **fixed-quota plan still on trial**.\n  Nothing is owed and the per-NIF checkout does not apply, because the quota already\n  includes this NIF: the act that unblocks Live is activating the plan.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRequiredResponse"}}}},"403":{"description":"Several distinct reasons, so branch on `error.code`:\n\n* **No `error.code` of the feature family** — you do not reach the account this request\n  resolves to (the `BeeL-Active-Company` header points at a company you neither own nor\n  manage with write access). Account existence is never disclosed.\n* `FEATURE_NOT_AVAILABLE` — you reach the account, but its plan does not include\n  `multi_nif` and it **already has at least one company**. The first NIF of an account\n  is always allowed (that is onboarding); every NIF after it needs the feature.\n  `error.details.feature_code` is `multi_nif` and `error.details.current_plan` carries\n  the account's current plan (`NONE` when it has none). `multi_nif` is contact-sales,\n  so there is no self-service plan to upgrade to and no `action`/upgrade target travels\n  in `details` — unlike the plan-based features, where it does.\n* `NOT_BILLING_OWNER` — creating a NIF **in Live** requires being the billing subject of\n  the account. A collaborator, or an API key minted by a member who is not the account's\n  owner, reaches the account and can create in Test, but cannot switch a NIF on in Live.\n  The owner has to do it, or mint the key.\n* `BILLING_MANAGED_BY_PROVIDER` — the account's billing is run by its provider, so\n  creating in Live has to be requested from them; retrying will not help.\n* `CONTACT_SALES_REQUIRED` — the account is on the API plan (billed per NIF) with **no\n  agreed price**, which covers one NIF self-service. Not a hard error: it routes to\n  sales, who set up a per-NIF price, after which the same request goes through.\n  `error.details.reason` says which rule applied and `error.details.salesUrl` is where\n  to talk to sales:\n  * `SECOND_NIF` — the account has exactly one active company and this would be the\n    second, in `TEST` or in `PROD`. Accounts that already have two or more are not held\n    back here.\n  * `LIVE_WITH_MULTIPLE_NIFS` — the request switches the NIF on in Live and the account\n    has two or more active companies. Answered before the `409` of a NIF live in\n    another account and before any `402`.\n\n`FEATURE_NOT_AVAILABLE` runs before the environment branches, so it applies to `TEST`\nand to `PROD` alike, and it runs *after* the duplicate-NIF check: re-sending a NIF you\nalready have is a `409`, never an upsell.\n\nThe two billing-subject codes only apply to a request that switches the NIF on in Live,\nand they are answered **before** the `409` of a NIF already live in another account and\nbefore any `402`: neither has an action you could take, so nothing about another\naccount's state is disclosed to someone who cannot act on it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"feature_not_available":{"summary":"The account's plan does not include multi_nif","value":{"success":false,"error":{"code":"FEATURE_NOT_AVAILABLE","message":"The 'Multi-NIF (Business Profiles)' feature is not available on your current plan.","details":{"feature_code":"multi_nif","current_plan":"PRO"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"contact_sales_second_nif":{"summary":"API plan without an agreed price asks for a second NIF","value":{"success":false,"error":{"code":"CONTACT_SALES_REQUIRED","message":"To add more than one NIF, talk to us: we will prepare a per-NIF price for you.","details":{"reason":"SECOND_NIF","salesUrl":"https://beel.es/ventas"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"contact_sales_live_with_multiple_nifs":{"summary":"API plan without an agreed price goes Live with several NIFs","value":{"success":false,"error":{"code":"CONTACT_SALES_REQUIRED","message":"To add more than one NIF, talk to us: we will prepare a per-NIF price for you.","details":{"reason":"LIVE_WITH_MULTIPLE_NIFS","salesUrl":"https://beel.es/ventas"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}}}},"404":{"description":"`SUBACCOUNT_USER_NOT_FOUND` — the person who holds the account no longer has an active\nuser (they deleted it), so there is nobody to own the new company. Nothing was created.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The NIF cannot be created. Branch on `error.code`:\n\n* `NIF_ALREADY_REGISTERED` — the NIF **already exists in your account**. Creating is\n  done; switching an environment on for it is a different act and has its own endpoint:\n  `POST /v1/companies/{company_id}/activations`. The existing\n  company's id travels in `error.details.company_id`, so no lookup is needed to call it.\n* `NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT` — the NIF is live in **another\n  account**. A NIF can only issue in production from one account (tax rule).\n* `SERIES_FORMAT_OVERLAPS` — the `numbering` block gives two series formats that could\n  print the same invoice number.\n* `NIF_LIMIT_REACHED` — the request switches the NIF on in Live, and the account's\n  agreement caps how many NIFs can be active in Live and the cap is full.\n  `error.details` is empty; the cap travels in `error.message`. Create it in Test, or\n  switch another NIF off in Live, or contact BeeL to raise the cap.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"nif_already_registered":{"summary":"The NIF already exists in your account","value":{"success":false,"error":{"code":"NIF_ALREADY_REGISTERED","message":"This NIF is already registered in your account","details":{"company_id":"9c8f1f2e-2b7a-4a1e-9d1f-3f5a8c2b7e10"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"nif_limit_reached":{"summary":"The agreement's cap of Live NIFs is full","value":{"success":false,"error":{"code":"NIF_LIMIT_REACHED","message":"You have reached the maximum of 5 active NIFs in Live included in your agreement. Contact BeeL to increase it.","details":{}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}}}},"422":{"description":"Validation or business rule error. The NIF is checked against the AEAT census before\nthe company is created, and nothing is stored when it fails (`NIF_VALIDATION_INVALID`).\nFor an individual the census matches the NIF with the fiscal name, so a wrong name is\nrejected too. For a legal entity the census checks the CIF only: the name is not\nverified, and it cannot fail because of the name. Check the NIF (and, for an\nindividual, the fiscal name), then repeat the request.\n`PHYSICAL_PERSON_CANNOT_HAVE_LEGAL_FORM` and `PHYSICAL_PERSON_CANNOT_HAVE_REPRESENTATIVE`\nreject an `INDIVIDUAL` that carries a `legal_form` or a `legal_representative`: both\nonly exist for a `LEGAL_ENTITY`. `ENTITY_TYPE_INCONSISTENT_WITH_NIF` rejects an\n`entity_type` that does not match the NIF: an `INDIVIDUAL` is identified by a DNI or NIE,\na `LEGAL_ENTITY` by a NIF that starts with a legal-form letter. `ACCOUNT_HAS_NO_HOLDER` — the account was provisioned\nwithout a person and nobody has claimed it yet, so there is no holder to own the\ncompany; issue a claim token first.\n\nOther `error.code` values with this status:\n\n- `INVALID_IRPF` — the `irpf_rate` is not a withholding rate in force (see `IrpfPercentage`); send one of the published rates.\n- `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.\n- `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.\n- `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.\n- `SERIES_FORMAT_INVALID_PADDING` — the padding in `{NUM:X}` must be a whole number from 1 to 20 (e.g. `{NUM:4}` → `0001`).\n- `SERIES_FORMAT_NUM_REQUIRED` — the series `format` has no `{NUM}` or `{NUM:X}`, so it cannot number invoices. Add the counter.\n- `SERIES_FORMAT_UNRECOGNIZED_VARS` — the `format` uses a variable BeeL. does not know. Allowed: `{CODIGO}`, `{YYYY}`, `{YY}`, `{MM}`, `{NUM}`, `{NUM:X}`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"description":"`EXTERNAL_SERVICE_ERROR` — the AEAT census could not be reached to validate the NIF. The company was not created. Nothing was\nstored. It is transient; repeat\nthe same request later (with the same `Idempotency-Key` if you sent one). No\n`Retry-After` header is sent.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"EXTERNAL_SERVICE_ERROR","message":"A technical error occurred. Please try again later.","details":{}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"},"type":"https://docs.beel.es/errors/EXTERNAL_SERVICE_ERROR","title":"EXTERNAL_SERVICE_ERROR","detail":"A technical error occurred. Please try again later.","instance":"/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/companies"}}}},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"get":{"operationId":"listCompanies","tags":["AccountCompanies"],"x-required-scopes":["companies:list"],"security":[{"ApiKeyAuth":[]},{"SessionCookie":[]},{"OAuth2":["companies:list"]}],"summary":"List the companies of an account","description":"Returns the companies (NIFs) belonging to the account in the path, ordered with the\nprimary company first. An account with no companies yet returns an empty list rather\nthan an error.\n\n- **`search`:** filters case-insensitively on NIF, legal name and trade name.\n- **`include=readiness`:** adds each company's issuing-readiness block.\n- **Paginated**, always: without `page` and `limit` you get the first 20 companies, not\n  all of them, and `pagination` is present in every response — a single-company account\n  simply gets a one-item page.\n- **Series:** not part of this response. Read them from\n  `GET /v1/companies/{company_id}/series`.\n","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"search","in":"query","required":false,"description":"Case-insensitive filter on NIF, legal name or trade name. Blank/omitted returns all.","schema":{"type":"string"}},{"name":"include","in":"query","required":false,"description":"Include derived data. `readiness` adds each company's issuing-readiness status.","schema":{"$ref":"#/components/schemas/CompanyInclude"}}],"responses":{"200":{"description":"List of companies","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCompanies200Response"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The request does not resolve an account you reach (no active account, or the\n`BeeL-Active-Company` header points at a company you neither own nor manage).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/stats":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyStats","x-required-scopes":["companies:list"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:list"]}],"summary":"Get invoice stats per company","description":"Returns, for each company of the account, how many fiscal documents it has\nissued and when it last issued one. What counts is described on `invoice_count` and\n`last_invoice_at` in the response.\n\n**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get\nthe stats of the first 20 companies, not of all of them. One row per company, over the same\nuniverse and in the same order as `GET /v1/accounts/{account_id}/companies` — `search`\nincluded — so asking both with the same `page`, `limit` and `search` lines the two\nresponses up company by company.\n","tags":["AccountCompanies"],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"search","in":"query","required":false,"description":"Case-insensitive filter on NIF, legal name or trade name — the same filter, over the same universe, as the one `GET /v1/accounts/{account_id}/companies` applies. Blank or omitted returns all.","schema":{"type":"string"}}],"responses":{"200":{"description":"Per-company invoice stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListCompanyStats200Response"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The request does not resolve an account you reach (no active account, or the\n`BeeL-Active-Company` header points at a company you neither own nor manage).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyById","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Get a company","description":"Returns the identity and activation state of a company: its fiscal data, whether it\nis switched on in Test and in Live, and its VeriFactu registration state.\n\nIt also returns **every field `PATCH /v1/companies/{company_id}` accepts** — contact\ndetails, legal representative, bank details, IAE, activity start date, payment term and\nthe rendering block — so what was written can be read back without keeping a copy of it.\nA field never set comes back absent: that means \"nothing stored\", not \"hidden\".\n\nIts invoice series are not part of this response: read them from\n`GET /v1/companies/{company_id}/series`.\n","tags":["Company"],"responses":{"200":{"description":"Company details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanyById","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Update a company","description":"Updates the editable fields of a company; the set is the one\n`UpdateCompanyRequest` declares.\n\n- **Immutable fields:** `nif`, `entity_type` and `legal_form`, once set. Sending one of\n  them with a different value answers `422` with a code that names the field; sending the\n  value it already has is not a change.\n- **`legal_name`:** changing it requires the NIF to pass an AEAT census re-validation. For a\n  legal entity (`LEGAL_ENTITY`) the census identifies the company by its NIF alone: the name\n  is not verified, so the name sent cannot make it fail. For an `INDIVIDUAL` the name must\n  match the one the census holds for that NIF.\n- **Census not answering:** if the AEAT census cannot be reached, the change is not\n  rejected. The response is `200` with the new `legal_name` stored, and BeeL repeats the\n  census check in the background. The outcome of that check is not part of this resource:\n  no field of `CompanyData` carries it. To know what the census says about a NIF and a name,\n  ask `POST /v1/nif/validate`.\n- **Addresses:** a Spanish postal code (`country_code` omitted or `ES`) must have 5 digits,\n  in `address` and in `legal_representative.address`; otherwise `422 POSTAL_CODE_INVALID_ES`.\n  Other countries' postal codes are free-form.\n\n## Test credentials on a Live company\n\nOnce the company is activated in Live, a test credential may only write the fields that\naffect how the invoice looks: `logo_url`, `invoice_accent_color`,\n`invoice_template_type`, `invoice_language`, `email_language` and `additional_info`. Any\nother field describes the real business — fiscal address, legal representative, bank\ndetails, contact data, IAE, activity start date, payment term — and answers\n`422 FISCAL_IDENTITY_LIVE_ONLY` from Test, since the company is a single record shared by\nboth modes. A company not activated in Live accepts the whole body from Test, and sending\na field its current value is never a change.\n\n## What comes back\n\nThe `200` returns `CompanyData` with **every field this request accepts**, under the same\nname and the same type — so the response is the confirmation of what was stored, and a\nlater `GET` says the same. A field you never set comes back absent, which means \"nothing\nstored\", not \"hidden\".\n\nTwo things live outside this body and keep their own reads: the invoice series\n(`GET /v1/companies/{company_id}/series`) and the rendering block, which is also served\non its own by `GET /v1/companies/{company_id}/invoice-customization`.\n","tags":["Company"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanyRequest"},"examples":{"patch_company_contact_and_bank":{"$ref":"#/components/examples/patch-contact-and-bank"}}}}},"responses":{"200":{"description":"Company updated. The body is `CompanyData`, which echoes every field this request\naccepts — the stored value, not the one you sent, so it is also the confirmation of\nwhat a partial update left untouched.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company with write access.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation or business rule error. These codes are specific to this operation:\n\n- `FISCAL_IDENTITY_LIVE_ONLY` — the call came from Test on a company that is\n  activated in Live and touched a field outside the six a test credential may\n  write (`logo_url`, `invoice_accent_color`, `invoice_template_type`,\n  `invoice_language`, `email_language`, `additional_info`). Repeat it with a live\n  key; nothing was written.\n- `IMMUTABLE_NIF`, `IMMUTABLE_ENTITY_TYPE`, `IMMUTABLE_LEGAL_FORM` — the request\n  changes `nif`, `entity_type` or `legal_form`, which cannot change once set. Nothing\n  was written; drop the field from the request.\n- `NIF_CENSUS_MISMATCH`, `NIF_DE_BAJA`, `NIF_REVOCADO` — the request changes\n  `legal_name` and the AEAT census re-validation rejects it: the census does not\n  recognise the NIF (for an `INDIVIDUAL`, with that name; for a legal entity the name is\n  not verified, only the NIF), or the NIF is deregistered or revoked. Nothing was\n  written.\n- `POSTAL_CODE_INVALID_ES` — a Spanish postal code in `address` or in\n  `legal_representative.address` does not have 5 digits. Nothing was written.\n- `INVALID_IBAN` — `default_iban` is not an IBAN: two letters, two digits and up to 30\n  letters or digits, 15 to 34 characters in all, once it is upper-cased and anything\n  that is not a letter or a digit is removed. Nothing was written.\n- `SWIFT_INVALID` — `default_swift` is not a SWIFT/BIC of 8 or 11 characters.\n  Nothing was written.\n- `PHYSICAL_PERSON_CANNOT_HAVE_LEGAL_FORM`, `PHYSICAL_PERSON_CANNOT_HAVE_REPRESENTATIVE`\n  — the company, as it would stand after the change, is an `INDIVIDUAL` with a\n  `legal_form` or a `legal_representative`; both only exist for a `LEGAL_ENTITY`.\n  Nothing was written.\n- `UNPROCESSABLE_ENTITY` — the generic validation failure, with the offending\n  field in `error.details`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"FISCAL_IDENTITY_LIVE_ONLY","message":"This company is live. In Test mode you can only change how the invoice looks (logo, colour, template, languages and additional information). Fiscal details are edited in Live mode"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyById","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Delete a company","description":"Removes a company from the account: it stops appearing and stops being billed.\n\n- **Existing invoices:** those already issued are retained, but the company-scoped API\n  can no longer resolve them once the NIF is removed.\n- **What blocks removal:** a NIF activated in Live\n  (`409 COMPANY_ACTIVE_IN_PRODUCTION`), one holding any invoice in Live — issued, draft\n  or proforma (`409 COMPANY_HAS_INVOICES`) — and the account's primary NIF\n  (`400 CANNOT_DELETE_PRIMARY`).\n- **Deactivating first:** switching off in Live is scheduled to the end of the paid\n  cycle, so the removal only becomes possible once that takes effect.\n- **Test:** NIFs never activated, or activated only in Test, are removed right away, and\n  invoices in Test never block.\n- **`Idempotency-Key`:** without one, a retry after a timeout answers `403` instead of\n  the original `204`.\n","tags":["Company"],"responses":{"204":{"description":"Company deleted"},"400":{"description":"The NIF is the primary company of the account (`CANNOT_DELETE_PRIMARY`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`ACTIVE_COMPANY_NOT_ACCESSIBLE` — your account does not own or manage this company with\nwrite access. A company that belongs to someone else, one already deleted and one that\nnever existed all answer the same way.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The company cannot be deleted:\n\n- `COMPANY_ACTIVE_IN_PRODUCTION` — it is active in Live. Deactivate it in Live and\n  delete it from the effective date of that deactivation.\n- `COMPANY_HAS_INVOICES` — it has invoices in Live (issued, draft or proforma).\n  Delete its drafts and proformas first; once it has issued, deactivate it instead.\n  Invoices in Test never block the deletion.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/issuing-readiness":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company whose issuing readiness is evaluated — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyIssuingReadiness","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Get the issuing readiness of a company","description":"Returns whether a company can issue its STANDARD invoice right now in the\nenvironment of the request, and the `blockers` that stop it otherwise. Readiness is a\nper-NIF property, evaluated independently for each company of the account.\n\n- **`ready`:** `true` only when `blockers` is empty.\n- **Activation:** issuing any fiscal document requires the company to be activated in the\n  environment of that document, whether or not it goes to VeriFactu.\n- **VeriFactu chain:** the AEAT census and signed representation are additionally\n  demanded only when the company is under the VeriFactu regime in this environment —\n  the same fact that decides, at issue time, whether its invoices are registered. A\n  company with VeriFactu off is ready with a NIF, a default series and an activation,\n  and the separate `verifactu` block reports the compliance chain independently.\n- **Not evaluated:** the account's quota or subscription, and the payload of any\n  particular invoice.\n","tags":["Company"],"responses":{"200":{"description":"Issuing readiness","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssuingReadinessResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/{company_id}/representation/generate":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account that owns the NIF. The pair must be coherent, and which answer you get depends on how far you reach: an `{account_id}` you do not reach answers `403` before the NIF is even looked at (the same answer an account that does not exist gets); a `{company_id}` you do not reach, or reach at a level that does not allow the operation — the usual case on writes, since the level checked is the one you hold over the `{account_id}` of the path — answers `403` too; and only a `{company_id}` you do reach but that hangs from a different account answers `404`, so the existence of a NIF in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"generateRepresentation","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/representation"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Generate the representation document","description":"Deprecated alias of `POST /v1/companies/{company_id}/representation`, with identical\nbehaviour.\n\n**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.\n","tags":["PublicCompanyRepresentations"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"PDF generated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationActionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/{company_id}/representation/download":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account that owns the NIF. The pair must be coherent, and which answer you get depends on how far you reach: an `{account_id}` you do not reach answers `403` before the NIF is even looked at (the same answer an account that does not exist gets); a `{company_id}` you do not reach, or reach at a level that does not allow the operation — the usual case on writes, since the level checked is the one you hold over the `{account_id}` of the path — answers `403` too; and only a `{company_id}` you do reach but that hangs from a different account answers `404`, so the existence of a NIF in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"downloadRepresentation","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/representation/document"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Download the representation document","description":"Deprecated alias of `GET /v1/companies/{company_id}/representation/document`, with\nidentical behaviour.\n\n**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.\n","tags":["PublicCompanyRepresentations"],"responses":{"200":{"description":"Presigned download URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationDownloadResponse"}}}},"400":{"description":"There is no document to download: `REPRESENTATION_NOT_FOUND` when no representation\ndocument has been generated for the company, `REPRESENTATION_DOCUMENT_NOT_STORED` when\nthe document has no stored PDF. In both cases, generate the document again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/{company_id}/representation/submit":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account that owns the NIF. The pair must be coherent, and which answer you get depends on how far you reach: an `{account_id}` you do not reach answers `403` before the NIF is even looked at (the same answer an account that does not exist gets); a `{company_id}` you do not reach, or reach at a level that does not allow the operation — the usual case on writes, since the level checked is the one you hold over the `{account_id}` of the path — answers `403` too; and only a `{company_id}` you do reach but that hangs from a different account answers `404`, so the existence of a NIF in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"submitRepresentation","x-successor":{"method":"POST","path":"/v1/companies/{company_id}/representation/submit"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Submit the signed representation document","description":"Deprecated alias of `POST /v1/companies/{company_id}/representation/submit`, with\nidentical behaviour.\n\n**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.\n","tags":["PublicCompanyRepresentations"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Signed PDF file"}}}}}},"responses":{"200":{"description":"PDF submitted for validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationActionResponse"}}}},"400":{"description":"The `file` part is missing from the multipart body (`MISSING_PART`).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/{company_id}/representation/status":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account that owns the NIF. The pair must be coherent, and which answer you get depends on how far you reach: an `{account_id}` you do not reach answers `403` before the NIF is even looked at (the same answer an account that does not exist gets); a `{company_id}` you do not reach, or reach at a level that does not allow the operation — the usual case on writes, since the level checked is the one you hold over the `{account_id}` of the path — answers `403` too; and only a `{company_id}` you do reach but that hangs from a different account answers `404`, so the existence of a NIF in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getRepresentationStatus","x-successor":{"method":"GET","path":"/v1/companies/{company_id}/representation"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Get the fiscal representation","description":"Deprecated alias of `GET /v1/companies/{company_id}/representation`, with identical\nbehaviour.\n\n**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.\n","tags":["PublicCompanyRepresentations"],"responses":{"200":{"description":"Representation status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationStatusResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/companies/{company_id}/representation/cancel":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account that owns the NIF. The pair must be coherent, and which answer you get depends on how far you reach: an `{account_id}` you do not reach answers `403` before the NIF is even looked at (the same answer an account that does not exist gets); a `{company_id}` you do not reach, or reach at a level that does not allow the operation — the usual case on writes, since the level checked is the one you hold over the `{account_id}` of the path — answers `403` too; and only a `{company_id}` you do reach but that hangs from a different account answers `404`, so the existence of a NIF in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"company_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"operationId":"cancelRepresentation","x-successor":{"method":"DELETE","path":"/v1/companies/{company_id}/representation"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Cancel the fiscal representation","description":"Deprecated predecessor of `DELETE /v1/companies/{company_id}/representation`. It cancels\nthe same representation, but answers `200` with a body where the canonical route answers\n`204`.\n\n**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.\n","tags":["PublicCompanyRepresentations"],"responses":{"200":{"description":"Representation cancelled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationActionResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/representation":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyRepresentation","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Get the fiscal representation","description":"Returns the state of the AEAT fiscal representation of a company: whether the\ndocument has been generated, signed and submitted, and whether AEAT accepted it or it was\ncancelled.\n\n- **`status`:** `NOT_STARTED`, `PDF_GENERATED`, `SUBMITTED`, `ACTIVE`, `ERROR` or\n  `CANCELLED`.\n- **Never started:** not an error. The endpoint answers `200` with `NOT_STARTED`, so\n  polling it is always safe.\n","tags":["CompanyRepresentation"],"responses":{"200":{"description":"Representation state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationStatusResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"generateCompanyRepresentation","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Generate the representation document","description":"Generates the unsigned AEAT representation PDF of a company, the first step of the\nrepresentation flow.\n\n- **Next steps:** download the PDF from\n  `GET /v1/companies/{company_id}/representation/document`, sign it digitally and return\n  it through `POST /v1/companies/{company_id}/representation/submit`.\n- **Fiscal identity:** must be complete before the document can be produced. An incomplete\n  one is rejected with `400` naming what is missing.\n- **Existing representation:** a company that already holds an active one is rejected too.\n  Cancel it first.\n","tags":["CompanyRepresentation"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Representation document generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationActionResponse"}}}},"400":{"description":"The document cannot be generated. Branch on `error.code`:\n\n* `REPRESENTATION_ALREADY_ACTIVE` — the company already holds an active representation.\n  Cancel it with `DELETE /v1/companies/{company_id}/representation` first.\n* `ALREADY_REGISTERED` — the VeriFactu provider already holds a signed, active\n  representation for this NIF that the company does not have in BeeL. Nothing is\n  generated on top of it; contact support.\n* `REPRESENTATION_DOCUMENT_GENERATION_FAILED` — the provider could not produce the\n  document. Nothing was stored; repeat the request later.\n\nAn incomplete fiscal identity is also answered with `400`, naming what is missing.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"cancelCompanyRepresentation","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Cancel the fiscal representation","description":"Cancels the active AEAT representation of a company.\n\n- **Effect:** until a new document is generated and signed, the company can no longer\n  submit invoices to AEAT in production. Its activation and its ability to issue\n  non-VeriFactu invoices are untouched.\n- **No active representation:** rejected with `400`. Cancelling is a state transition, not\n  a delete-if-present.\n","tags":["CompanyRepresentation"],"responses":{"204":{"description":"Representation cancelled. No body — the resulting state is a constant (`CANCELLED`) and `GET /v1/companies/{company_id}/representation` is its source of truth."},"400":{"description":"The representation cannot be cancelled. Branch on `error.code`:\n\n* `REPRESENTATION_NOT_ACTIVE` — the company holds no active representation to cancel.\n* `NIF_NOT_CONFIGURED` — the company has no NIF to cancel the representation for.\n* `REPRESENTATION_CANCELLATION_FAILED` — the provider could not cancel it with the AEAT.\n  Nothing changed; repeat the request later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/representation/document":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"downloadCompanyRepresentationDocument","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Download the representation document","description":"Returns a presigned URL, valid for 5 minutes, to download the representation PDF of a\ncompany.\n\n- **Which copy:** while the document is unsigned it serves the generated one; once the\n  signed copy has been submitted it serves that.\n- **Not generated yet:** a company that has not generated the document is rejected with\n  `400`.\n","tags":["CompanyRepresentation"],"responses":{"200":{"description":"Presigned download URL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationDownloadResponse"}}}},"400":{"description":"There is no document to download: `REPRESENTATION_NOT_FOUND` when no representation\ndocument has been generated for the company, `REPRESENTATION_DOCUMENT_NOT_STORED` when\nthe document has no stored PDF. In both cases, generate the document again.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/representation/submit":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"submitCompanyRepresentation","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Submit the signed representation document","description":"Uploads the digitally signed representation PDF of a company, sent as\n`multipart/form-data` in the `file` field.\n\n- **Validation:** the signature is checked with the provider within this call, so a\n  rejected signature answers `400 PDF_SIGNATURE_INVALID`. Poll\n  `GET /v1/companies/{company_id}/representation` for the resulting state.\n","tags":["CompanyRepresentation"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Signed PDF file"}}}}}},"responses":{"200":{"description":"Signed document submitted for validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepresentationActionResponse"}}}},"400":{"description":"The signed document was not accepted. Nothing changed. Branch on `error.code`:\n\n* `SIGNING_ONLY_IN_LIVE` — the request runs in Test (a `beel_sk_test_*` key or the\n  `BeeL-Environment: sandbox` header). The document is signed with a real certificate\n  and registered with the real AEAT, so it is only accepted in Live.\n* `NIF_NOT_CONFIGURED` — the company has no NIF to sign for.\n* `PDF_EMPTY` — the `file` field is empty.\n* `PDF_TOO_LARGE` — the file is over 10 MB. A request over 12 MB is cut earlier with\n  `413`.\n* `PDF_INVALID_EXTENSION` — the file name does not end in `.pdf`.\n* `PDF_INVALID_FORMAT` — the content is not a PDF.\n* `NO_UNSIGNED_DOCUMENT` — there is no generated, unsigned document to sign. Generate\n  it with `POST /v1/companies/{company_id}/representation` first.\n* `PDF_SIGNATURE_INVALID` — the provider rejected the signature. `error.message`\n  carries the reason when the AEAT gave one.\n* `PDF_VALIDATION_FAILED` — the provider could not be reached to check the signature.\n  Repeat the request later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/logo":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"put":{"operationId":"uploadCompanyLogoById","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Upload the logo of a company","description":"Uploads the logo printed on the invoices issued by a company, sent as\n`multipart/form-data` in the `file` field and replacing the previous one if there was any.\n\n- **Formats:** JPEG and PNG, up to 1 MB.\n- **Processing:** the image is validated, resized to fit within 300x300 pixels keeping\n  its aspect ratio, and stored. The response carries the resulting `logo_url`.\n","tags":["CompanyBranding"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary","description":"Logo image file (JPEG or PNG, up to 1 MB)."}}}}}},"responses":{"200":{"description":"Logo uploaded","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/CompanyLogo"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"422":{"description":"The image was rejected: `FILE_TOO_LARGE` when it is over 1 MB, or a type or content\nerror such as `INVALID_FILE_TYPE`. Also `FILE_EMPTY` (no bytes), `INVALID_EXTENSION`\n(the file name does not end in a JPEG or PNG extension), `INVALID_CONTENT` (the bytes\nare not the image type declared) and `FILE_CORRUPTED` (it could not be decoded to be\nresized). A file over 12 MB answers `413` before it is read.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanyLogoById","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Delete the logo of a company","description":"Removes the logo of a company. Invoices rendered afterwards carry no logo, and\nalready issued documents are unchanged. Deleting an absent logo also returns `204`.\n","tags":["CompanyBranding"],"responses":{"204":{"description":"Logo deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/invoice-customization":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyInvoiceCustomization","x-required-scopes":["companies:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:read"]}],"summary":"Get the invoice customization of a company","description":"Returns how the invoices of a company are rendered and delivered: PDF template,\naccent colour, invoice language, email language and current logo. Customization is a\nper-NIF property, so each company of the account carries its own.\n\nThe catalogue of available templates and suggested colours is served by\n`GET /v1/invoice-customization-options`.\n","tags":["CompanyBranding"],"responses":{"200":{"description":"Invoice customization","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/InvoiceCustomization"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"operationId":"updateCompanyInvoiceCustomization","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Update the invoice customization of a company","description":"Updates how the invoices of a company are rendered and delivered: PDF template,\naccent colour, invoice language and email language. Only the properties present in the\nrequest body are modified, and the logo is managed through the `logo` sub-resource.\n\nThe change applies to invoices rendered after it and does not alter already issued\ndocuments.\n","tags":["CompanyBranding"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateInvoiceCustomizationRequest"},"examples":{"update_invoice_customization":{"$ref":"#/components/examples/invoice-customization"}}}}},"responses":{"200":{"description":"Invoice customization updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/InvoiceCustomization"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold write access\nover it.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"$ref":"#/components/responses/StateConflict"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/verifactu-configuration":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyVeriFactuConfiguration","tags":["CompanyVeriFactuConfiguration"],"summary":"Get the VeriFactu configuration of a company","description":"Retrieves the VeriFactu configuration of this company. The configuration belongs to\nthe NIF, so the NIF in the path is what decides which one is returned.\n","x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Configuration retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VeriFactuConfiguration"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"operationId":"updateCompanyVeriFactuConfiguration","tags":["CompanyVeriFactuConfiguration"],"summary":"Update the VeriFactu configuration of a company","description":"Replaces the VeriFactu configuration of a company.\n\n- **Writable fields:** only `enabled`, and it is required — this is a full replacement, not\n  a partial merge. The rest of the returned configuration is resolved server-side.\n- **Turning it on registers the NIF for VeriFactu submission in the same call**,\n  atomically: if the registration is refused nothing is persisted and the response carries\n  the reason. In Live it requires a signed and validated AEAT representation first, or\n  `422 VERIFACTU_REPRESENTATION_REQUIRED`.\n- **Sandbox is always on:** `enabled: false` there answers\n  `422 VERIFACTU_ALWAYS_ON_IN_SANDBOX`.\n\n## Turning it off\n\nSetting `enabled` to false stops sending this company's invoices to AEAT and starts\nderegistering the NIF from VeriFactu submission. It does not deactivate the\ncompany: the activation is a fact of its own for the (company, environment) pair, so the\ncompany keeps issuing in that environment and stays `ready`. Releasing the NIF — and in\nLive freeing it for another account — is always\n`DELETE /v1/companies/{company_id}/activations`.\n","x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVeriFactuConfigurationRequest"},"examples":{"enable_veri_factu":{"$ref":"#/components/examples/verifactu-enable"},"disable_veri_factu":{"$ref":"#/components/examples/verifactu-disable"}}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/VeriFactuConfiguration"}}}]}}}},"400":{"description":"Turning VeriFactu on registers the NIF with the VeriFactu provider in this same call; when\nthat registration cannot happen nothing is persisted. Branch on `error.code`:\n\n* `COMPANY_NIF_MISSING` — the company has no NIF to register.\n* `API_COMMUNICATION_ERROR` — the provider refused the registration or could not be\n  reached. `error.message` carries the provider's reason when it gave one; repeat the\n  request once it is fixed or later.\n\nA body that cannot be parsed is also answered with `400`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/tax-configuration":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyTaxConfiguration","tags":["CompanyTaxConfiguration"],"summary":"Get the tax configuration of a company","description":"Returns the tax configuration of a company: its default main tax (`IVA`, `IGIC`,\n`IPSI` or `OTHER`) with the default percentage and regime key, the default exemption\nreason, its IRPF and equivalence surcharge settings, and the default payment method and\npayment term.\n\nThe catalogue of tax types this configuration draws from is not company data and lives\noutside this resource.\n","x-required-scopes":["configuration:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:read"]}],"responses":{"200":{"description":"Configuration retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TaxConfiguration"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"operationId":"updateCompanyTaxConfiguration","tags":["CompanyTaxConfiguration"],"summary":"Update the tax configuration of a company","description":"Updates the tax configuration of a company. Fields you omit keep their current\nvalue; `default_main_tax`, when sent, replaces the stored one wholesale.\n\n- **Regime coherence:** the main tax and its VeriFactu regime key must be coherent. Regime\n  key `18` (equivalence surcharge) only exists for `IVA`, so pairing it with any other\n  regime answers `422 INVALID_REGIME_KEY_FOR_TAX_TYPE`, with `details` naming the rejected\n  key, the tax type and the keys that type admits.\n- **Surcharge:** applying the surcharge without regime key `18` answers `422`\n  `RECARGO_REQUIRES_REGIME_RE`.\n- **Exemption reason:** `default_exemption_reason` travels with `default_main_tax` —\n  sending the tax without a reason clears the stored one, and sending only the reason\n  applies it to the tax already stored.\n- **Coherence of the result:** the configuration as it stands after the change is checked\n  as a whole, and answers `422` with one of these codes:\n  `MAIN_TAX_REQUIRED` (there is no `default_main_tax`, stored or sent),\n  `IRPF_REQUIRED_WHEN_NOT_EXEMPT` (`apply_irpf` without `irpf_exempt` and no\n  `default_irpf_rate`), `RECARGO_EQUIVALENCIA_REQUIRED` (`apply_equivalence_surcharge`\n  without `default_equivalence_surcharge`), `RECARGO_IVA_COMBINATION_INVALID` (a surcharge\n  rate that does not go with the main IVA rate), `EXEMPTION_REASON_NOT_ALLOWED_AS_DEFAULT`\n  (a `default_exemption_reason` that only makes sense line by line),\n  `EXEMPTION_REASON_REQUIRES_ZERO_RATE` (an exemption reason on a main tax that is not\n  0%), `ZERO_RATE_REQUIRES_EXEMPTION_REASON` (a 0% main tax with no reason) or\n  `EXEMPTION_REASON_TEXT_REQUIRED` (a reason that needs `default_exemption_reason_text`\n  and has none).\n- **First write:** two concurrent requests that both create the configuration answer the\n  loser with `409 TAX_CONFIG_CONFLICT`; retrying it updates the one that won.\n","x-required-scopes":["configuration:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["configuration:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTaxConfigurationRequest"},"examples":{"update_tax_configuration_professional":{"$ref":"#/components/examples/tax-professional"},"update_tax_configuration_retailer":{"$ref":"#/components/examples/tax-retailer"}}}}},"responses":{"200":{"description":"Configuration updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TaxConfiguration"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`TAX_CONFIG_CONFLICT` — another request created this company's tax configuration at\nthe same time. Retry: it now finds the configuration and updates it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n\nOther `error.code` values with this status:\n\n- `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.\n- `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.\n- `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.\n- `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.\n- `REGIME_KEY_NOT_SUPPORTED` — BeeL. cannot declare that `regime_key` to AEAT (e.g. `06`, `14`); use another key.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/series":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanySeries","tags":["CompanySeries"],"summary":"List the invoice series of a company","description":"Returns the invoice series of a company.\n\n- **Filters:** `active` restricts to active or inactive series — omit it and you get all of\n  them. `document_type` returns the series of that type, the only ones that can number\n  it.\n- **Pagination (opt-in):** send `page` and/or `limit` to receive a single page plus a\n  `data.pagination` block with the totals. Omit both and the response carries the full list\n  in `data.series` and no `pagination` block.\n","x-required-scopes":["series:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:read"]}],"parameters":[{"name":"active","in":"query","description":"Filters by activity: `true` returns only active series, `false` only inactive ones.\nOmit it and you get **all** the series, active and inactive.\n","schema":{"type":"boolean"}},{"name":"document_type","in":"query","description":"Filter by document type","schema":{"$ref":"#/components/schemas/DocumentType"}},{"name":"page","in":"query","required":false,"description":"Page number (starts at 1). Omit for the full, unpaginated list.","schema":{"type":"integer","minimum":1},"example":1},{"name":"limit","in":"query","required":false,"description":"Items per page. Omit for the full, unpaginated list.","schema":{"type":"integer","minimum":1,"maximum":100},"example":20}],"responses":{"200":{"description":"Series retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["series"],"properties":{"series":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSeries"}},"pagination":{"allOf":[{"$ref":"#/components/schemas/Pagination"}],"description":"Totals of the requested page. Present ONLY when the request sent\n`page` and/or `limit`; omitted for the historical full list.\n"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"post":{"operationId":"createCompanySeries","tags":["CompanySeries"],"summary":"Create an invoice series for a company","description":"Creates an invoice series under a company.\n\n- **Document type:** `document_type` is required. `UNASSIGNED` is rejected with\n  `422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`: corrective invoices need a series of their own.\n- **Code:** must be unique within the company; a code already taken answers `409`.\n- **Unique numbers per issuer:** a series whose `code` and `format` could print a number\n  that another series of the company (in the same environment, active or not) can also\n  print is rejected with `409 SERIES_FORMAT_OVERLAPS`, naming that series. Proforma series\n  are not compared.\n- **Numbering:** `format` must contain `{NUM}` or `{NUM:X}` and only accepts uppercase\n  tokens. `counter_reset` defaults to `ANNUAL`, so a format with no year token has to be\n  sent with `counter_reset: NEVER`.\n- **Default series:** the first series created for a document type is marked as default\n  even if you send `default_series: false`.\n","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSeriesRequest"},"examples":{"main_series":{"$ref":"#/components/examples/main_series"},"international_series":{"$ref":"#/components/examples/international_series"}}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Series created successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]},"examples":{"series_created_success":{"$ref":"#/components/examples/created-success-4"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The series clashes with another series of the company: `SERIES_CODE_DUPLICATED` (the\ncode is taken) or `SERIES_FORMAT_OVERLAPS` (it could print numbers the other series\nprints).\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The series is rejected. `error.code` is `VALIDATION_ERROR` when the body parses but a value\nis not acceptable: `details` is a flat map from the offending property's name (in the\ncontract's `snake_case`) to a message describing what is wrong with it, or follows\n`FieldDeserializationError` when the value is outside an enum's vocabulary. Otherwise it is\none of the series rules, among them:\n- `SERIES_MONTHLY_REQUIRES_MONTH_AND_YEAR` / `SERIES_ANNUAL_REQUIRES_YEAR` — the format\n  cannot tell the counter reset periods apart.\n- `SERIES_INITIAL_NUMBER_OUT_OF_RANGE` — the initial number is out of range.\n- `SERIES_FORMAT_NUMBER_TOO_LONG` — the longest number the series can generate (the\n  format with the code, the year, the month and the counter substituted) exceeds the 60\n  characters the AEAT accepts for the invoice number. The counter counts as at least 9\n  digits, with or without padding: `{NUM:X}` is a minimum width and the number keeps\n  growing past it.\n- `SERIES_FORMAT_INVALID_CHARACTERS` — that number would contain a character the AEAT does\n  not accept: only printable ASCII (32-126) is allowed, except `\"`, `'`, `<`, `>` and `=`.\n- `SERIES_TYPE_CHANGE_LEAVES_TYPE_UNCOVERED` — changing `document_type` of the default\n  series would leave its current document type, which still has other active series,\n  without a default. Make one of those the default first.\n- `SERIES_COMPANY_DELETED` — the company was deleted while the request was running: its\n  series keep their numbering but can no longer be activated or made the default.\n\nOther `error.code` values with this status:\n\n- `SERIES_FORMAT_INVALID_PADDING` — the padding in `{NUM:X}` must be a whole number from 1 to 20 (e.g. `{NUM:4}` → `0001`).\n- `SERIES_FORMAT_NUM_REQUIRED` — the series `format` has no `{NUM}` or `{NUM:X}`, so it cannot number invoices. Add the counter.\n- `SERIES_FORMAT_UNRECOGNIZED_VARS` — the `format` uses a variable BeeL. does not know. Allowed: `{CODIGO}`, `{YYYY}`, `{YY}`, `{MM}`, `{NUM}`, `{NUM:X}`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"SERIES_FORMAT_NUMBER_TOO_LONG","message":"The number this series generates can reach 64 characters and the AEAT accepts at most 60 (series and number together). Shorten the code or the fixed text of the format"},"meta":{"timestamp":"2026-09-24T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/series/defaults":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"getCompanyDefaultSeries","tags":["CompanySeries"],"summary":"Get the default series of a company","description":"Reports, for each `DocumentType` used by automatic invoicing flows, whether the company\n(NIF) has a default invoice series and which one: `exists`, plus the `series_id` when there\nis one.\n\n- **No default:** that document type cannot be issued without naming a `series_id`\n  explicitly, and automatic flows skip it with\n  `failure.payment.skip.missing_default_series`.\n- **Environment:** resolved from the request context; it takes no input.\n\n**Closed catalogue.** This collection is fixed and bounded — one entry per `DocumentType`:\nit carries no `pagination`, it takes no `page`/`limit`, and every response holds the whole\nset.\n","x-closed-catalogue":true,"x-required-scopes":["series:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:read"]}],"responses":{"200":{"description":"Default series status retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["defaults"],"properties":{"defaults":{"type":"array","items":{"$ref":"#/components/schemas/DocumentTypeDefaultStatus"}}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"put":{"operationId":"ensureCompanyDefaultSeries","tags":["CompanySeries"],"summary":"Ensure a default series per document type","description":"Ensures the company has a default invoice series for `STANDARD`, `SIMPLIFIED` and\n`CORRECTIVE` in the current environment, and returns the resulting set. The request takes\nno body: the desired end state is one default per document type, so repeating it changes\nnothing.\n\n- **Already there:** a document type that already has a default keeps it, and it is\n  returned unchanged.\n- **Missing:** it is created with code `F`, `S` or `R` and format\n  `{CODIGO}-{YYYY}-{NUM:4}`, active and marked as default.\n- **Code taken:** if that code already belongs to another series, the document type is\n  omitted from the response and is left with no default.\n\n**Closed catalogue.** This collection is fixed and bounded — one entry per `DocumentType`:\nit carries no `pagination`, it takes no `page`/`limit`, and every response holds the whole\nset.\n","x-closed-catalogue":true,"x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Default series ensured","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","required":["series"],"properties":{"series":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSeries"}}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/series/{series_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"series_id","in":"path","required":true,"description":"Series ID","schema":{"$ref":"#/components/schemas/UUID"}}],"get":{"operationId":"getCompanySeries","tags":["CompanySeries"],"summary":"Get an invoice series","description":"Returns one invoice series of a company, with its code, format, counter state,\ndocument type and whether it is the default of that type.\n","x-required-scopes":["series:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:read"]}],"responses":{"200":{"description":"Series retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No series with that id under this company. A series belonging to another NIF answers\nthe same way: under the scope of the NIF in the path it simply does not exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"operationId":"patchCompanySeries","tags":["CompanySeries"],"summary":"Update an invoice series","description":"Updates only the fields present in the body, leaving every other field of the series as\nit is.\n\n- **Clearing a field:** a field sent as `null` is cleared, which only `description`\n  supports.\n- **Numbering fields:** `code`, `format`, `counter_reset` and `initial_number` are rejected\n  once the series has issued invoices (`numbering_locked` is `true`).\n- **Unique numbers per issuer:** changing `code`, `format` or `document_type` so that the\n  series could print a number another series of the company can also print is rejected\n  with `409 SERIES_FORMAT_OVERLAPS`.\n- **`default_series`:** it cannot be used to clear the default. Sending `false` for the\n  series that currently is the default answers `DEFAULT_CANNOT_BE_UNMARKED`, because it\n  would leave the document type with active series and no default, and issuing without an\n  explicit `series_id` would then fail with `SERIES_DEFAULT_NOT_FOUND`. Hand the default\n  over with `PUT /v1/companies/{company_id}/series/{series_id}/default` on the new series,\n  which unmarks the previous one. Sending `false` for a series that is not the default is a\n  no-op.\n","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchSeriesRequest"},"examples":{"update_series_description":{"$ref":"#/components/examples/update_description"},"update_series_format":{"$ref":"#/components/examples/change_format"}}}}},"responses":{"200":{"description":"Series updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"description":"The update is rejected. Error codes:\n- `SERIES_CODE_LOCKED_HAS_INVOICES`, `SERIES_FORMAT_LOCKED_HAS_INVOICES`,\n  `SERIES_RESET_LOCKED_HAS_INVOICES`, `SERIES_INITIAL_NUMBER_LOCKED_HAS_INVOICES`,\n  `SERIES_DOCUMENT_TYPE_LOCKED_HAS_INVOICES` — the series already consumed a number\n  (`numbering_locked` is `true`)\n- `DEFAULT_CANNOT_BE_UNMARKED` — `default_series: false` on the series that\n  currently is the default; promote another one with\n  `PUT .../series/{series_id}/default` instead\n- `DEFAULT_CANNOT_DEACTIVATE` — `active: false` on the default series\n- `INACTIVE_CANNOT_BE_DEFAULT` — `default_series: true` on an inactive series\n- `REFERENCED_BY_PAYMENT_CONNECTION` — deactivating a series a payment\n  connection is configured to use\n- `REFERENCED_BY_RECURRING_INVOICE` — deactivating a series with a live\n  (ACTIVE or PAUSED) recurring invoice template using it. Finish it or switch it\n  to another series first\n- `SERIES_NUMBERING_FROZEN` — the series issued its first number while this change\n  was being applied, so its numbering can no longer change. Read it again: the\n  `*_LOCKED_HAS_INVOICES` codes above name the field\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No series with that id under this company. A series belonging to another NIF answers\nthe same way: under the scope of the NIF in the path it simply does not exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"`SERIES_FORMAT_OVERLAPS` — with the change, the series could print numbers another series\nof the company prints. `SERIES_DEFAULT_CONFLICT` — another request made a different\nseries the default for the same document type at the same time; read the series\nagain and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/SeriesValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"deleteCompanySeries","tags":["CompanySeries"],"summary":"Delete an invoice series","description":"Soft-deletes an invoice series, deactivating it first if it is active.\n\n- **The code is not released:** it stays taken after the deletion because it identifies the\n  invoices already issued under it, so recreating a series with the same code answers\n  `409 SERIES_CODE_DUPLICATED`.\n- **Default series:** it cannot be deleted while another active series of the same document\n  type exists — promote that other one first. If it is the only series of its type it is\n  deleted and the type is left with none, a valid state in which issuing without an\n  explicit `series_id` answers `SERIES_DEFAULT_NOT_FOUND`.\n","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"responses":{"204":{"description":"Series deleted successfully"},"400":{"description":"The series cannot be deleted. `error.code` says why:\n\n- `DEFAULT_CANNOT_DELETE` — it is the default of its document type and another\n  active series of that same type exists. Promote that one as default first.\n- `HAS_INVOICES_CANNOT_DELETE` — it has invoices associated. Remove or discard\n  those invoices first.\n- `REFERENCED_BY_PAYMENT_CONNECTION` — it is configured in a payment connection.\n  Point that connection at another series first.\n- `REFERENCED_BY_RECURRING_INVOICE` — a live (ACTIVE or PAUSED) recurring invoice\n  template uses this series. Finish it or switch it to another series first.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No series with that id under this company. A series belonging to another NIF answers\nthe same way: under the scope of the NIF in the path it simply does not exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/series/{series_id}/default":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"series_id","in":"path","required":true,"description":"Series ID to mark as default","schema":{"$ref":"#/components/schemas/UUID"}}],"put":{"operationId":"setCompanyDefaultSeries","tags":["CompanySeries"],"summary":"Mark a series as default","description":"Marks an invoice series as the default of its document type for this company, and\nunmarks the previous one.\n\n- **One per type:** only one series can be the default per company and document type.\n- **Must be active:** an inactive series is rejected with `400`.\n- **Idempotent:** repeating the call changes nothing.\n","x-required-scopes":["series:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["series:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Series marked as default successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/InvoiceSeries"}}}]}}}},"400":{"description":"Cannot mark as default (inactive series)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company, or does not hold the required\naccess over it. A NIF that does not exist answers the same way.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No series with that id under this company. A series belonging to another NIF answers\nthe same way: under the scope of the NIF in the path it simply does not exist.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Another request marked a different series of the same document type as the default at the\nsame time. Re-read the series and retry if the outcome is not the intended one.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/activations":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company being switched on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"activateCompanyById","x-no-location":"The activation is company state, not an addressable resource: it has no GET of its own — read it on GET /v1/companies/{company_id}.","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"summary":"Switch a company on","description":"Switches an existing company on in the mode carried in the body. The mode is always\nexplicit and never taken from the credential's environment, so a Test key can switch a NIF\non in Live.\n\n## Modes and billing\n\n- **`TEST`:** immediate and free.\n- **`PROD`:** immediate when the account already has a card on file or an enterprise\n  contract, and the NIF is added to the existing subscription. With no card on file it\n  answers `402 CHECKOUT_REQUIRED`, returning a `checkout_url` when `success_url` and\n  `cancel_url` are supplied. It also requires being the billing subject of the account\n  (`403 NOT_BILLING_OWNER` otherwise).\n- **`billing_period`:** picks the plan the checkout sells — `MONTHLY` (default) or\n  `ANNUAL`. It only matters when a checkout is opened. Accounts with a negotiated rate or\n  an enterprise contract cannot buy the annual plan (`422 ANNUAL_PLAN_NOT_AVAILABLE`).\n\n## Idempotency and pending switch-offs\n\n- **Repeating the call:** opens no second checkout and adds no second subscription item; it\n  returns the existing activation with `already_active: true`. The same `Idempotency-Key`\n  sent to this route and to the nested one it replaces is the same operation, so it is\n  replayed and never charged twice.\n- **A pending switch-off is cancelled:** while it is pending the NIF is still on — it just\n  carries an effective date — so switching it on again only removes that date, answers\n  `scheduled_deactivation_cancelled: true`, and charges or credits nothing. It does commit\n  the NIF to the next billing cycle, though, so under an agreement capped at a number of\n  Live NIFs it asks for a seat like a new NIF would, and is rejected with\n  `409 NIF_LIMIT_REACHED` when the cap is already full.\n","tags":["CompanyActivations"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateCompanyRequest"},"example":{"environment":"PROD"}}}},"responses":{"201":{"description":"The NIF is switched on in that mode.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyActivationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Switching on in Live requires payment. `CHECKOUT_REQUIRED` means there is no card on file yet; `error.details.checkout_url` carries the checkout when `success_url` and `cancel_url` were supplied. `PAYMENT_REQUIRED` means billing exists but is unpaid (payment retries are exhausted), and the outstanding invoice has to be settled first. A failed charge that is still being retried does not block Live.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"`ACTIVE_COMPANY_NOT_ACCESSIBLE` — your account does not own or manage this NIF, or does not hold write access over it. A NIF that does not exist answers exactly the same. `NOT_BILLING_OWNER` — switching a NIF on in Live requires being the billing subject of the account. `BILLING_MANAGED_BY_PROVIDER` — the account's billing is run by its provider, so the switch has to be requested from them; retrying will not help. `CONTACT_SALES_REQUIRED` — the account is on the API plan (billed per NIF) with no agreed price and has two or more active companies: going Live needs a per-NIF price agreed with sales first, and nothing is switched on and no checkout is opened. Not a hard error: `error.details.reason` is `LIVE_WITH_MULTIPLE_NIFS` and `error.details.salesUrl` is where to talk to sales. It applies from the first NIF in Live; with a single active company, or once sales configures the agreed price, the same request goes through. Answered before the `409` of a NIF live in another account and before any `402`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"contact_sales_live_with_multiple_nifs":{"summary":"API plan without an agreed price goes Live with several NIFs","value":{"success":false,"error":{"code":"CONTACT_SALES_REQUIRED","message":"To add more than one NIF, talk to us: we will prepare a per-NIF price for you.","details":{"reason":"LIVE_WITH_MULTIPLE_NIFS","salesUrl":"https://beel.es/ventas"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}}}},"409":{"description":"The NIF cannot be switched on in Live. Branch on `error.code`:\n\n* `NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT` — another account already has this NIF in\n  Live. A NIF can only invoice in Live from one account, so the other one has to switch\n  it off first.\n* `NIF_LIMIT_REACHED` — the account's agreement caps how many NIFs can be active in Live,\n  and the cap is full. It applies to a NIF that does not hold a seat yet: a new one, one\n  whose switch-off already took effect, or one whose pending switch-off this call would\n  cancel. `error.details` is empty; the cap travels in `error.message`. Switch another\n  NIF off in Live, or contact BeeL to raise the cap. Test is never capped.\n\nNothing is switched on in either case.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"nif_prod_already_active_in_another_account":{"summary":"The NIF is live in another account","value":{"success":false,"error":{"code":"NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT","message":"This NIF is already registered in production under another account. A NIF can only issue real invoices from a single account.","details":{}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"nif_limit_reached":{"summary":"The agreement's cap of Live NIFs is full","value":{"success":false,"error":{"code":"NIF_LIMIT_REACHED","message":"You have reached the maximum of 5 active NIFs in Live included in your agreement. Contact BeeL to increase it.","details":{}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}}}},"422":{"description":"`ACCOUNT_HAS_NO_HOLDER` — the account was provisioned without a person and nobody has claimed it yet, so there is no holder to attach the activation to. Issue a claim token with `POST /v1/accounts/{account_id}/claim-tokens` first. `CHECKOUT_URLS_REQUIRED` — switching on in Live with no card on file opens a checkout, and that needs both `success_url` and `cancel_url`. `ANNUAL_PLAN_NOT_AVAILABLE` — `billing_period: ANNUAL` was requested but this account cannot buy the annual plan (a negotiated rate, an enterprise contract, or no annual plan offered); switch on without `billing_period` to buy the monthly plan. Nothing was activated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["CompanyActivations"],"summary":"Switch a company off","operationId":"deactivateCompanyById","description":"Switches the company off in the mode given by `environment`; the other mode is\nuntouched.\n\n- **Sealed, not deleted:** the activation's history survives. After the switch-off takes\n  effect the NIF can neither issue nor correct invoices in that mode until it is switched\n  on again, and in Live that sealing is what releases the NIF for another account.\n\n## When it takes effect\n\n- **In Live the switch-off is scheduled, not immediate:** the cycle is paid up front, so\n  the response carries an `effective_at` and the NIF keeps invoicing until then. Nothing is\n  refunded. `effective_at` is the end of the current billing cycle, unless the NIF was\n  switched on within that same cycle, in which case it is the end of the next one.\n- **`TEST`, and `PROD` under an enterprise contract:** immediate, and answer with no\n  `effective_at`.\n\n## Repeats and permissions\n\n- **Repeating the call:** on a mode whose switch-off is already pending it returns the same\n  date with `already_scheduled: true`; switching off a mode that was never on is a silent\n  no-op.\n- **Permission:** switching off in Live requires being the billing subject of the account.\n","x-required-scopes":["companies:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["companies:write"]}],"parameters":[{"name":"environment","in":"query","required":true,"schema":{"$ref":"#/components/schemas/Environment"},"description":"Mode to switch the NIF off in."}],"responses":{"200":{"description":"Switch-off registered — scheduled in Live, immediate otherwise.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CompanyDeactivation"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"get":{"operationId":"listCompanyPaymentConnections","x-closed-catalogue":true,"x-required-scopes":["payment-connections:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:read"]}],"summary":"List the payment connections of a NIF","description":"Returns the payment provider connections of a company your account **owns or\nmanages**, with the provider-side account each one points at and its `status`. Use it to\ncheck whether a NIF you provisioned has completed its connection.\n\n- **A NIF with no connections:** answers `200` with an empty list.\n- **`environment`:** Test and Live connections are independent, so only the ones living in\n  the mode of the key you ask with are returned; this field states which.\n\n**Closed catalogue.** A NIF is not limited to one connection per provider: within a single\nenvironment it may hold several of the same provider, one per external account. What is\nunique is the external account itself — one live connection per provider, environment and\nexternal account. The set is still bounded and unpaginated: the collection carries no\n`pagination` and takes no `page`/`limit`, and every response holds the whole set for the\nenvironment of the key you ask with.\n","tags":["CompanyPaymentConnections"],"responses":{"200":{"description":"Connections of the NIF","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListManagedPaymentConnectionsResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company. The same answer is given for a\nNIF that does not exist.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/authorizations":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the authorization is opened for — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"initiatePaymentConnection","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Open an authorization to connect a payment provider","description":"Opens an authorization session so the holder of a company your account **manages**\ncan connect a payment provider (`stripe`), and returns the `authorization_url` where they\nauthorize it.\n\n- **`return_url`:** once the holder authorizes, BeeL's callback finalizes the connection\n  and redirects back to the `return_url` of your portal, if you supplied one, with the\n  parameters described under `return_url`.\n- **When the connection appears:** it is created only when the holder authorizes, so it\n  does not appear in `GET /v1/companies/{company_id}/payment-connections` until then. It\n  is sealed under the NIF in the path, so auto-invoicing issues under that NIF.\n- **The NIF must be activated in the mode of your API key** (`beel_sk_test_*` → Test,\n  `beel_sk_live_*` → Live); otherwise the request answers `400`\n  `COMPANY_NOT_ACTIVATED_IN_ENVIRONMENT` and no `authorization_url` is issued, because\n  without activation there is no invoice series or tax configuration to invoice with.\n  Test and Live activations are independent — a NIF activated in one mode still needs\n  activating in the other.\n- **One provider account, one NIF:** a provider account (`acct_...`) can be connected to a\n  single NIF across the whole platform. Authorizing the same provider account from a second\n  NIF does not move it: the callback fails with\n  `OAUTH_ACCOUNT_CONNECTED_TO_OTHER_COMPANY`, and the existing connection keeps invoicing\n  under the NIF it was sealed with. To move it, first\n  `DELETE /v1/companies/{company_id}/payment-connections/{connection_id}` on the NIF\n  that holds it, then open a new authorization on the NIF you want it under.\n","tags":["CompanyPaymentConnections"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiatePaymentConnectionRequest"},"examples":{"initiate_stripe_connection":{"$ref":"#/components/examples/initiate-stripe"}}}}},"responses":{"200":{"description":"Authorization URL generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiatePaymentConnectionResponse"}}}},"400":{"description":"The NIF is not activated in the mode of your API key\n(`COMPANY_NOT_ACTIVATED_IN_ENVIRONMENT`). Activate it in that mode and retry;\nno authorization is opened and no authorization URL is issued.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company (`ACTIVE_COMPANY_NOT_ACCESSIBLE`). No\nauthorization is opened.\n\n`MANAGED_ACCOUNT_HOLDER_REQUIRED` — the company belongs to a managed account that has\nno holder yet (provisioned without a person), so there is nobody to attribute the\nself-billed invoices to. Have the holder claim the account first, then connect.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation or business rule error. One code is specific to this operation:\n\n- `PROVIDER_NOT_SUPPORTED` — `provider` is not one BeeL connects to (today, only\n  `stripe`). Nothing is opened.\n- `VALIDATION_ERROR` — the generic validation failure, with the offending field in\n  `error.details`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}":{"parameters":[{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PaymentConnectionIdParam"}],"patch":{"operationId":"updateCompanyPaymentConnection","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Update the payment connection settings of a NIF","description":"Updates the auto-invoicing settings of the payment connection named by `{connection_id}` of a\ncompany your account **owns or manages**.\n\n- **Partial by field:** a field you omit keeps its current value. The series fields also\n  accept an explicit `null`, which clears the series and falls back to the company default\n  for that document type. `filter_config` is the exception: when sent, it **replaces the\n  whole object**, not just the sub-fields you included — a partial `filter_config` clears\n  every filter axis you left out.\n- **Read-only fields:** `id`, `provider`, `status`, `environment`, `external_account_id`,\n  `connected_at`, `last_event_at` and `active_filters` are not part of this request and are\n  ignored if sent. `status` moves through the disconnect operation, never here.\n- **Series:** each one must exist, be active, belong to this NIF and carry a compatible\n  document type, or the request answers `422`.\n","tags":["CompanyPaymentConnections"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanyPaymentConnectionRequest"},"examples":{"update_payment_connection_settings":{"$ref":"#/components/examples/update-settings"}}}}},"responses":{"200":{"description":"Connection updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyPaymentConnectionResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company. The same answer is given for a\nNIF that does not exist. Nothing is updated.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/PaymentConnectionNotFound"},"422":{"description":"A setting is not acceptable, and nothing is updated. Branch on `error.code`:\n\n- `CONNECTION_SERIE_NOT_FOUND` — a series in `series` does not exist or belongs to\n  another NIF.\n- `CONNECTION_SERIE_INACTIVE` — the series exists but is inactive.\n- `CONNECTION_SERIE_TIPO_INCOMPATIBLE` — the series' document type does not fit the\n  slot it was sent for.\n- `FILTER_PATTERNS_TOO_MANY` — more than 20 patterns in one of\n  `excluded_description_patterns` or `required_description_patterns`.\n- `FILTER_PATTERN_TOO_LONG` — a pattern over 200 characters.\n- `FILTER_PATTERN_INVALID` — a pattern that is not a valid regular expression.\n- `FILTER_CATEGORY_NOT_ACTIONABLE` — `disabled_categories` names `REFUND` or\n  `CREDIT_NOTE`, which the rules never evaluate, so disabling them would do nothing.\n- `VALIDATION_ERROR` — a field breaks its declared constraint.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"operationId":"disconnectCompanyPaymentConnection","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Disconnect the payment provider of a NIF","description":"Disconnects the payment connection named by `{connection_id}` of a company that your account\n**owns or manages**.\n\n- **Effect:** BeeL deletes the stored credentials and auto-invoicing stops at once; charges\n  arriving afterwards are ignored and produce no invoice. Already-issued invoices are not\n  affected.\n- **The provider-side authorization is not revoked:** to withdraw it, the holder must\n  remove BeeL's access from the provider's own dashboard (in Stripe, *Settings → Connected\n  applications*).\n","tags":["CompanyPaymentConnections"],"responses":{"204":{"description":"Connection disconnected"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"Your account does not own or manage this company. The same answer is given for a\nNIF that does not exist.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/PaymentConnectionNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"}],"get":{"operationId":"listCompanyPaymentEvents","x-default-sunset":{"cohort":"payments-cleanup","parameter":"charges_only"},"x-required-scopes":["payment-connections:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:read"]}],"summary":"List the payment events of a company's connection","description":"Lists the payment events received through the payment provider connection of a NIF\n(company), most recent first. Use it to audit the charges that produced an invoice and to\nfind the ones that did not.\n\n- **By default, every event is listed.** Nothing is hidden: events the connection\n  skipped, duplicates and disputes are all returned. Narrow the list with the filters\n  below; what you do not filter, you get. Set `charges_only=true` to read the same events\n  as one row per money movement instead.\n- **Scope:** events belong to the connection, not to the NIF directly. The\n  `{connection_id}` segment picks one connection of the NIF in the path, and only the\n  events of that connection are returned; an event of another NIF of the same account is\n  never reachable from here.\n- **Unknown connection:** a `{connection_id}` that belongs to no connection of this NIF\n  returns `404`.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"status","in":"query","description":"Keep only the events in these processing states. Repeat the parameter to combine\nstates; omit it, or send it empty (`status=`), for all of them.\n","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PaymentEventStatus"}},"style":"form","explode":true},{"name":"failure_reason","in":"query","description":"Keep only the events that did not complete for these reasons. Repeat the parameter to\ncombine reasons. An empty value (`failure_reason=`) is the same as omitting it.\n","required":false,"schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"failure_category","in":"query","description":"Keep only the events that did not complete for a cause in these categories. Repeat the\nparameter to combine categories. Events that completed carry no category and are\ntherefore never kept by this filter. An empty value (`failure_category=`) is the same\nas omitting it.\n","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PaymentEventFailureCategory"}},"style":"form","explode":true},{"name":"event_kind","in":"query","description":"Keep only the events of these kinds. Matches `event_kind`, never `event_type`: the\nkind is what the event is about, while `event_type` is the raw name the provider\nemitted (`payment_intent.succeeded`) and is not filterable. `UNKNOWN` keeps every\nevent whose provider name we do not classify. Repeat the parameter to combine kinds.\nAn empty value (`event_kind=`) is the same as omitting it.\n","required":false,"schema":{"type":"array","items":{"$ref":"#/components/schemas/PaymentEventType"}},"style":"form","explode":true},{"name":"min_amount","in":"query","description":"Keep only the events whose `amount` is at or above this value.","required":false,"schema":{"type":"number","minimum":0}},{"name":"max_amount","in":"query","description":"Keep only the events whose `amount` is at or below this value.","required":false,"schema":{"type":"number","minimum":0}},{"name":"needs_action","in":"query","description":"`true` keeps only the events still worth acting on; `false`, only the ones that are\nnot. Omit it for both.\n","required":false,"schema":{"type":"boolean"}},{"name":"from","in":"query","description":"Keep only the events received at or after this instant.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","description":"Keep only the events received at or before this instant.","required":false,"schema":{"type":"string","format":"date-time"}},{"name":"q","in":"query","description":"Free-text search over the payer name and the provider identifiers of the charge\n(`pi_`, `ch_`, `cs_`, `evt_`). Case-insensitive, partial matches allowed. The payer\nemail is deliberately not searchable.\n","required":false,"schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"include_discarded","in":"query","description":"Include the events you discarded. They are excluded by default; discarding is a\ndecision about the list, not a state of the event.\n","required":false,"schema":{"type":"boolean","default":false}},{"name":"charges_only","in":"query","description":"Return one row per money movement instead of one row per event. Today, when this\nparameter is omitted or `false`, every event is listed.\n\n**The default changes on 11 December 2026.** From that day, omitting this parameter\nreads the listing as `charges_only=true` — one row per money movement. Until then a\nrequest that omits it answers with `Deprecation`, `Sunset` and `Link` headers. Send\nthe value you want explicitly, whichever it is, so the change of default cannot\nsurprise you. See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n\nA money movement is a sale, a failed payment, each refund and each dispute. The\nprovider usually reports a single movement through several events. When this\nparameter is `true`, each movement is returned in at most two rows: its outcome and,\nwhen any of its events requires action, its incident. The outcome row stands for the\nevents of the movement that require no action; the incident row stands for the events\nof the movement that require action, so an invoiced movement that still has something\nto resolve always shows it. Within each row, the event that produced an invoice comes\nfirst, then an event of a classified kind before an unclassified one, and then the\nmost recent one. A sale and a failed payment of the same charge are two movements, and\nevery refund and every dispute of a charge is a movement of its own; the opening and\nthe closing of a dispute are the same movement.\n\nAn event of an unclassified kind that requires action joins the incident row of the\nmovement its identifier names: a charge joins its sale, a dispute joins that dispute,\nand a refund or a credit note joins that refund. An event whose identifier names no\nmovement, or that carries no identifier at all, stays a row of its own and is never\nmerged with another. Events that moved no money, such as a customer, a price or a\nproduct being created, are left out, except those that require action, which are\nalways listed.\n\nDiscarded events of a movement that is still listed through a live event are ignored: they are\nneither returned nor counted. A movement whose events are all discarded is returned as\na single row, only when `include_discarded` is `true`, and counts once in `discarded`.\nWithout other filters, the number of rows returned with `include_discarded=true` is\ntherefore `total` plus `discarded`.\n\nThe other filters narrow the rows returned and `pagination.total_items`, and nothing\nelse. `counts` describes the whole connection in the view you asked for and disregards\nevery other filter: with `charges_only=true`, its `total`, `discarded`, `needs_action`\nand `by_status` values count money movements rather than individual events, while its\n`ignored` and `failure_reasons` values keep counting events. A request that filters by\n`q` may therefore return a single row while `counts.total` still reports every movement\nof the connection.\n","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Payment events of the connection","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListManagedPaymentEventsResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/PaymentConnectionNotFound"},"422":{"description":"The request cannot be answered as written:\n\n- `EVENT_FILTER_RANGE_INVALID`: a range came inverted — `min_amount` above\n  `max_amount`, or `from` after `to`. Both ends are inclusive, so equal values are\n  fine. The range is rejected rather than applied because an inverted one matches\n  nothing, and an empty list would be indistinguishable from having no events.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"get":{"operationId":"getCompanyPaymentEvent","x-required-scopes":["payment-connections:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:read"]}],"summary":"Get a payment event of a company's connection","description":"Retrieves a single payment event of the NIF's connection, including the outcome of its\nautomatic invoicing and, when it failed, the stable failure code you can act on.\n\n- **Not found:** an event that does not belong to this NIF's connection returns `404`,\n  the same answer an event that does not exist gets, so an event of another NIF is never\n  disclosed.\n","tags":["CompanyPaymentEvents"],"responses":{"200":{"description":"Payment event","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}/retry":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"post":{"operationId":"retryCompanyPaymentEvent","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Retry a payment event of a company's connection","description":"Reprocesses a payment event whose automatic invoicing did not complete, applying the\nconfiguration of the NIF as it stands now. Use it after fixing what caused the failure,\nfor example a missing invoice series.\n\n- **`retry_available`:** only events where it is `true` can be retried. Read it instead\n  of deriving retryability from `status` yourself; anything else returns `400`.\n- **Limit:** the status and the skip reason must admit reprocessing, and the event must\n  still be under the limit of 3 retries (`retry_count`). A retry that fails for a\n  transient cause outside the event (provider outage, timeout) does not count towards\n  the limit.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Event reprocessed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}}}},"400":{"description":"The event cannot be retried: its state does not admit a retry\n(`EVENT_INVALID_STATUS_FOR_RETRY`), it exhausted its retries\n(`EVENT_MAX_RETRIES_EXCEEDED`), or it no longer holds the original provider payload\n(`EVENT_SOURCE_PAYLOAD_UNAVAILABLE`), in which case retrying will never succeed.\nIt also answers `CONNECTION_NOT_ACTIVE` when the connection is no longer active: both\noperations talk to the provider again, so a disconnected connection can still be read\nbut not operated.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}/draft":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"post":{"operationId":"generateCompanyPaymentEventDraft","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Create a draft invoice from a payment event","description":"Builds a draft invoice from a payment event that could not be invoiced automatically,\napplying the same recipient resolution and tax treatment the automatic flow would have\napplied, under the NIF in the path.\n\n- **Draft only:** the document is not issued, not numbered against the series and not\n  emailed. Issue it yourself once it is right.\n- **Eligible events:** only those that produced no invoice can produce a draft; otherwise\n  the request returns `400`.\n- **Rejected documents:** if invoicing rules reject the resulting document the request\n  returns `422` and no draft is created.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Draft invoice created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventDraftResponse"}}}},"400":{"description":"The event state does not allow generating a draft\n(`EVENT_INVALID_STATUS_FOR_DRAFT`), or the event no longer holds the original provider\npayload the draft would be built from (`EVENT_SOURCE_PAYLOAD_UNAVAILABLE`).\nIt also answers `CONNECTION_NOT_ACTIVE` when the connection is no longer active: both\noperations talk to the provider again, so a disconnected connection can still be read\nbut not operated.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"description":"Invoicing rules rejected the draft (`EVENT_DRAFT_NOT_POSSIBLE`, with the pipeline\nreason as message argument), for example a payment in a currency other than EUR. A\npayment over 3,000 € without the customer's tax data does produce a draft: a\n`STANDARD` invoice whose recipient you complete before issuing it.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}/resolve":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"post":{"operationId":"resolveCompanyPaymentEvent","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Mark a payment event as manually resolved","description":"Marks a payment event as resolved outside BeeL, for example when the invoice was issued\nthrough another tool or the situation was otherwise handled by hand. The event leaves the\nevents that need action without generating any invoice.\n\nThe operation applies to the whole payment the event belongs to. Its scope is every active\nevent of the connection that shares the payment identity of the event named in the\nrequest: when the event carries a payment identifier (`external_payment_id`), every event\nwith that same identifier, whatever its kind (the sale, its failed attempts, its refunds);\notherwise, when it carries a source object (`source_object_id`, such as a credit note or a\ndispute), every event with that same source object; otherwise, the event alone. Within that\nscope, the events that need action (the same criterion as the `needs_action` filter) and\nare eligible are resolved together, in a single transaction; a failed event still pending\nautomatic retry is resolved as well, so that no retry is attempted for a payment the\ncaller has declared handled elsewhere. Events that do not need action — for instance an\nevent still in `RECEIVED` state that has not stalled — remain unchanged. The response\ncarries the event named in the request.\n\n- **Eligible events:** only events in `FAILED`, `SKIPPED` or `RECEIVED` can be resolved;\n  if the event named in the request is not eligible, the request returns `400`.\n- **Terminal:** a resolved event cannot be retried afterwards.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Event marked as resolved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}}}},"400":{"description":"The event state does not allow resolving it (`EVENT_INVALID_STATUS_FOR_RESOLVE`) —\nit is already invoiced or already resolved.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}/discard":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"post":{"operationId":"discardCompanyPaymentEvent","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Discard a payment event","description":"Removes a payment event from the default list (soft delete). The event stays in the audit\ntrail and can be brought back with the restore operation.\n\nThe operation applies to the whole payment the event belongs to. Its scope is every active,\neligible event of the connection that shares the payment identity of the event named in\nthe request: when the event carries a payment identifier (`external_payment_id`), every\nevent with that same identifier, whatever its kind (the sale, its failed attempts, its\nrefunds); otherwise, when it carries a source object (`source_object_id`, such as a credit\nnote or a dispute), every event with that same source object; otherwise, the event alone.\nAll of them are discarded together, in a single transaction and with the same `deleted_at`\ntimestamp. Events that are not eligible remain unchanged, and so do events still in\n`RECEIVED` state other than the one named in the request: they have not been processed\nyet and are left for processing. The response carries the event named in the request.\n\n- **Eligible events:** an event already linked to an issued invoice, or currently being\n  processed, cannot be discarded; the request returns `400`.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Event discarded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}}}},"400":{"description":"The event state does not allow discarding it (`EVENT_INVALID_STATUS_FOR_DISCARD`) —\nit is already invoiced or currently being processed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/companies/{company_id}/payment-connections/{connection_id}/events/{event_id}/restore":{"parameters":[{"$ref":"#/components/parameters/CompanyIdParam"},{"$ref":"#/components/parameters/PaymentConnectionIdParam"},{"$ref":"#/components/parameters/EventIdParam"}],"post":{"operationId":"restoreCompanyPaymentEvent","x-required-scopes":["payment-connections:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["payment-connections:write"]}],"summary":"Restore a discarded payment event","description":"Reverses a previous discard, bringing a payment event back into the default list.\n\nThe operation reverses the whole discard operation: the event named in the request and\nevery event with the same payment identity (see the discard operation) that was discarded\ntogether with it, that is, with exactly the same `deleted_at` timestamp. Events discarded\nin an earlier or later operation are not affected. Idempotent — restoring an event that is\nnot discarded is a no-op.\n","tags":["CompanyPaymentEvents"],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Event restored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/CompanyNotAccessible"},"404":{"$ref":"#/components/responses/EventNotOfThisConnection"},"422":{"$ref":"#/components/responses/UnprocessableEntity"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks":{"post":{"tags":["Webhooks"],"summary":"Create a webhook subscription","description":"**Deprecated.** Use `POST /v1/accounts/{account_id}/webhooks`, which behaves identically.\n\nRegisters an HTTPS endpoint to receive notifications for the event types listed\nin `events`.\n\n- **`secret`:** returned **only** in this response and never again. Store it before\n  discarding the body; deliveries are signed with it and carry the signature in the\n  `BeeL-Signature` header.\n- **`test_delivery`:** a one-off signed delivery sent to your URL as part of creating\n  the subscription, so you learn whether your endpoint answers without a second call.\n  It is best effort: the subscription exists and is active whatever it says, and the\n  field is `null` when the test could not be run at all.\n- **`account_relationship`:** which accounts the subscription receives events from —\n  `own` (the default), `managed`, or `all`.\n- **Limits:** an account holds at most **10 active subscriptions**; creating an\n  eleventh is rejected with `400` `WEBHOOK_ACTIVE_SUBSCRIPTION_LIMIT_REACHED`. Registering the same URL twice creates two subscriptions, and\n  the endpoint then receives each event twice.\n\n**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.\n","operationId":"createWebhookSubscription","x-successor":{"method":"POST","path":"/v1/accounts/{account_id}/webhooks"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookSubscriptionRequest"},"example":{"url":"https://yourapp.com/webhooks/beel","events":["verifactu.status.updated"]}}}},"callbacks":{"webhookEvent":{"{$request.body#/url}":{"post":{"summary":"Webhook event delivered to your endpoint","description":"BeeL. sends HTTP POST to your URL when a subscribed event occurs.\nYour endpoint must respond with HTTP 2xx within 10 seconds.\nFailed deliveries are retried on a fixed schedule: 7 attempts over about 3 days\n(3 attempts over about 11 minutes in sandbox).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"},"examples":{"verifactuAccepted":{"summary":"VeriFactu submission accepted by AEAT","value":{"id":"3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c","type":"verifactu.status.updated","created_at":"2026-03-07T16:20:26Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"660e8400-e29b-41d4-a716-446655440001","operation":"REGISTRATION","previous_status":"PENDING","new_status":"ACCEPTED","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=2025-001&fecha=07-03-2026&importe=121.00","qr_base64":"iVBORw0KGgoAAAANSUhEUgAAAMg...","invoice_hash":"B11F3A015173AD99075E2720F61E2DE1FF08CBFEDD85C6F73C77AD835301B2A3","error_code":null,"error_message":null}}},"verifactuRejected":{"summary":"VeriFactu submission rejected by AEAT","value":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","type":"verifactu.status.updated","created_at":"2026-03-07T16:22:10Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"660e8400-e29b-41d4-a716-446655440001","operation":"REGISTRATION","previous_status":"PENDING","new_status":"REJECTED","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=2025-001&fecha=07-03-2026&importe=121.00","qr_base64":"iVBORw0KGgoAAAANSUhEUgAAAMg...","invoice_hash":"7E4C2B19A0D35F8E6B1C9D2A4F7E0B3C5D8A1F6E9B2C4D7A0E3F5B8C1D4A6E9F","error_code":"1105","error_message":"NIF del emisor no registrado en VeriFactu"}}},"verifactuVoided":{"summary":"Cancellation of a voided invoice accepted by AEAT","value":{"id":"b2c3d4e5-f6a7-4890-bcde-f12345678901","type":"verifactu.status.updated","created_at":"2026-03-08T09:05:41Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","operation":"VOID","previous_status":"PENDING","new_status":"VOIDED","error_code":null,"error_message":null}}}}}}},"responses":{"2XX":{"description":"Your endpoint acknowledged the event successfully."},"4XX":{"description":"Client error — delivery will NOT be retried, except `408` and `429`, which follow the retry schedule."},"5XX":{"description":"Server error — delivery will be retried on the retry schedule."}},"parameters":[{"name":"BeeL-Signature","in":"header","required":true,"description":"HMAC-SHA256 of the delivery, in the format `t=<timestamp>,v1=<signature>`. Verify it before trusting the delivery.","schema":{"type":"string","example":"t=1741362026,v1=3c4f7a2e1b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f"}},{"name":"BeeL-Event","in":"header","required":true,"description":"Event type, identical to the `type` field of the payload.","schema":{"type":"string","example":"verifactu.status.updated"}},{"name":"BeeL-Event-Id","in":"header","required":true,"description":"Webhook event UUID — identical across all retries of the same logical event. Matches the `id` field of the payload.","schema":{"type":"string","format":"uuid"}},{"name":"BeeL-Delivery-Id","in":"header","required":true,"description":"Delivery attempt UUID — unique per HTTP call, including retries. Matches the `id` of the delivery log in the dashboard.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Same UUID as `BeeL-Event-Id`. Lets the receiver deduplicate deliveries automatically.","schema":{"type":"string","format":"uuid"}}]}}}},"responses":{"201":{"description":"Webhook subscription created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionWithSecret"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/WebhookSubscriptionValidationError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"get":{"tags":["Webhooks"],"summary":"List the webhook subscriptions","description":"**Deprecated.** Use `GET /v1/accounts/{account_id}/webhooks`, which behaves identically.\n\nReturns the webhook subscriptions of the authenticated account, active\nand inactive alike. The signing secrets are never included.\n\n**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.\n","operationId":"listWebhookSubscriptions","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/webhooks"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"List of webhook subscriptions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks/{webhook_id}":{"get":{"tags":["Webhooks"],"summary":"Get a webhook subscription","description":"**Deprecated.** Use `GET /v1/accounts/{account_id}/webhooks/{webhook_id}`, which behaves identically.\n\nReturns a single webhook subscription. The signing secret is never included.\n\n**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.\n","operationId":"getWebhookSubscription","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Webhook subscription","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscription"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["Webhooks"],"summary":"Update a webhook subscription","description":"**Deprecated.** Use `PATCH /v1/accounts/{account_id}/webhooks/{webhook_id}`, which behaves identically.\n\nUpdates the fields present in the body — `url`, `events`, `active`,\n`account_relationship` — and leaves the rest untouched.\n\n- **`events`:** replaces the whole list, it does not add to it, so an event left out\n  of it stops being delivered.\n- **`active`:** setting it to `false` stops deliveries without discarding the delivery\n  history. A subscription we turned off ourselves (`deactivated_by: beel`) needs a\n  successful test delivery before it can be turned back on. Turning a subscription\n  back on counts towards the limit of 10 active subscriptions, and is rejected with\n  `400` `WEBHOOK_ACTIVE_SUBSCRIPTION_LIMIT_REACHED` when ten are already active.\n- **Signing secret:** not touched here. Rotate it with\n  `POST /v1/accounts/{account_id}/webhooks/{webhook_id}/secret`.\n\n**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.\n","operationId":"updateWebhookSubscription","x-successor":{"method":"PATCH","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookSubscriptionRequest"}}}},"responses":{"200":{"description":"Webhook subscription updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscription"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/WebhookSubscriptionValidationError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook subscription","description":"**Deprecated.** Use `DELETE /v1/accounts/{account_id}/webhooks/{webhook_id}`, which behaves identically.\n\nPermanently deletes a webhook subscription. No further events are\ndelivered to its URL. To stop deliveries reversibly, set `active` to\n`false` instead.\n\n**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.\n","operationId":"deleteWebhookSubscription","x-successor":{"method":"DELETE","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"Subscription deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks/{webhook_id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List the delivery attempts of a webhook","description":"**Deprecated.** Use `GET /v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries`, which behaves identically.\n\nReturns the delivery attempts of this subscription, newest first. Each entry records\none attempt with the response it got, so a retried event appears once per attempt.\n\n- **`event_type`:** narrows the list to a single event type.\n- **`event_id`:** follows one event across every attempt made on it, without paging\n  through the whole history.\n\n**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.\n","operationId":"listWebhookDeliveries","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"parameters":[{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"event_type","in":"query","required":false,"description":"Only deliveries of this event type.","schema":{"type":"string"},"example":"invoice.issued"},{"name":"event_id","in":"query","required":false,"description":"Only deliveries of this event. Use it to follow every attempt on one event without paging through the whole history.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delivery attempts, newest first.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryLog"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry":{"post":{"tags":["Webhooks"],"summary":"Retry a failed webhook delivery","description":"**Deprecated.** Use `POST /v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry`, which behaves identically.\n\nRe-sends the original payload of a delivery immediately.\n\n- **Payload:** the one captured when the event happened, not a fresh snapshot, so\n  changes made to the entity since then are not reflected.\n- **History:** the outcome is recorded as a new entry and the original entry is kept\n  as it was. `attempt_number` continues the same sequence, so it can exceed the number\n  of automatic attempts.\n\n**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.\n","operationId":"retryWebhookDelivery","x-successor":{"method":"POST","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Retry result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookDeliveryLog"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks/{webhook_id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test event to a webhook endpoint","description":"**Deprecated.** Use `POST /v1/accounts/{account_id}/webhooks/{webhook_id}/test`, which behaves identically.\n\nSends a synthetic payload to the subscription's URL immediately, outside the normal\ndelivery queue. Use it to verify that your endpoint is reachable and handles\ndeliveries correctly before you rely on real events.\n\n- **Payload:** carries `\"test\": true` and synthetic data, and is signed like any other\n  delivery, so it also exercises your signature check.\n- **Retries:** none. A failed test is not retried and does not appear in the delivery\n  history.\n- **`Idempotency-Key`:** repeating the call with the same key returns the cached\n  result without sending the test payload again.\n- **Result:** read `delivery_success`; a delivery your endpoint rejected is still a\n  successful test run, not an error.\n\n**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.\n","operationId":"testWebhookSubscription","x-successor":{"method":"POST","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}/test"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The test was attempted. Check `delivery_success` for the outcome: a delivery your\nendpoint rejected is still a successful test run, not an error.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookTestResult"}}}]},"examples":{"success":{"summary":"Endpoint responded with 2xx","value":{"success":true,"data":{"delivery_success":true,"http_status":200,"duration_ms":142,"error":null}}},"failure":{"summary":"Endpoint returned an error","value":{"success":true,"data":{"delivery_success":false,"http_status":503,"duration_ms":318,"error":"HTTP 503","failure_cause":"http_server_error"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/StateConflict"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/webhooks/{webhook_id}/secret":{"post":{"tags":["Webhooks"],"summary":"Rotate the signing secret of a webhook","description":"**Deprecated.** Use `POST /v1/accounts/{account_id}/webhooks/{webhook_id}/secret`, which behaves identically.\n\nGenerates a new HMAC signing secret for a webhook subscription.\n\n- **Old secret:** **immediately invalidated**. Update your signature verification\n  logic before rotating, to avoid missing events during the transition.\n- **New secret:** returned **once**, in this response only. It cannot be read again.\n\n**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.\n","operationId":"rotateWebhookSecret","x-successor":{"method":"POST","path":"/v1/accounts/{account_id}/webhooks/{webhook_id}/secret"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"},{"name":"webhook_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Secret rotated — new secret returned (only shown once)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionWithSecret"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}}],"post":{"tags":["AccountWebhooks"],"summary":"Create a webhook subscription","description":"Registers an HTTPS endpoint to receive notifications for the event types listed\nin `events`.\n\n- **`secret`:** returned **only** in this response and never again. Store it before\n  discarding the body; deliveries are signed with it and carry the signature in the\n  `BeeL-Signature` header.\n- **`test_delivery`:** a one-off signed delivery sent to your URL as part of creating\n  the subscription, so you learn whether your endpoint answers without a second call.\n  It is best effort: the subscription exists and is active whatever it says, and the\n  field is `null` when the test could not be run at all.\n- **`account_relationship`:** which accounts the subscription receives events from —\n  `own` (the default), `managed`, or `all`.\n- **Limits:** an account holds at most **10 active subscriptions**; creating an\n  eleventh is rejected with `400` `WEBHOOK_ACTIVE_SUBSCRIPTION_LIMIT_REACHED`. Registering the same URL twice creates two subscriptions, and\n  the endpoint then receives each event twice.\n","operationId":"createAccountWebhookSubscription","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookSubscriptionRequest"},"example":{"url":"https://yourapp.com/webhooks/beel","events":["verifactu.status.updated"]}}}},"callbacks":{"webhookEvent":{"{$request.body#/url}":{"post":{"summary":"Webhook event delivered to your endpoint","description":"BeeL. sends HTTP POST to your URL when a subscribed event occurs.\nYour endpoint must respond with HTTP 2xx within 10 seconds.\nFailed deliveries are retried on a fixed schedule: 7 attempts over about 3 days\n(3 attempts over about 11 minutes in sandbox).\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"},"examples":{"verifactuAccepted":{"summary":"VeriFactu submission accepted by AEAT","value":{"id":"3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c","type":"verifactu.status.updated","created_at":"2026-03-07T16:20:26Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"660e8400-e29b-41d4-a716-446655440001","operation":"REGISTRATION","previous_status":"PENDING","new_status":"ACCEPTED","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=2025-001&fecha=07-03-2026&importe=121.00","qr_base64":"iVBORw0KGgoAAAANSUhEUgAAAMg...","invoice_hash":"B11F3A015173AD99075E2720F61E2DE1FF08CBFEDD85C6F73C77AD835301B2A3","error_code":null,"error_message":null}}},"verifactuRejected":{"summary":"VeriFactu submission rejected by AEAT","value":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","type":"verifactu.status.updated","created_at":"2026-03-07T16:22:10Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"660e8400-e29b-41d4-a716-446655440001","operation":"REGISTRATION","previous_status":"PENDING","new_status":"REJECTED","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=2025-001&fecha=07-03-2026&importe=121.00","qr_base64":"iVBORw0KGgoAAAANSUhEUgAAAMg...","invoice_hash":"7E4C2B19A0D35F8E6B1C9D2A4F7E0B3C5D8A1F6E9B2C4D7A0E3F5B8C1D4A6E9F","error_code":"1105","error_message":"NIF del emisor no registrado en VeriFactu"}}},"verifactuVoided":{"summary":"Cancellation of a voided invoice accepted by AEAT","value":{"id":"b2c3d4e5-f6a7-4890-bcde-f12345678901","type":"verifactu.status.updated","created_at":"2026-03-08T09:05:41Z","api_version":"YYYY-MM","livemode":true,"company_id":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f","nif":"B12345674","account_id":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b","account_external_ref":"acct-2041","account_relationship":"own","data":{"invoice_id":"550e8400-e29b-41d4-a716-446655440000","invoice_number":"2025-001","verifactu_registration_id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","operation":"VOID","previous_status":"PENDING","new_status":"VOIDED","error_code":null,"error_message":null}}}}}}},"responses":{"2XX":{"description":"Your endpoint acknowledged the event successfully."},"4XX":{"description":"Client error — delivery will NOT be retried, except `408` and `429`, which follow the retry schedule."},"5XX":{"description":"Server error — delivery will be retried on the retry schedule."}},"parameters":[{"name":"BeeL-Signature","in":"header","required":true,"description":"HMAC-SHA256 of the delivery, in the format `t=<timestamp>,v1=<signature>`. Verify it before trusting the delivery.","schema":{"type":"string","example":"t=1741362026,v1=3c4f7a2e1b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f"}},{"name":"BeeL-Event","in":"header","required":true,"description":"Event type, identical to the `type` field of the payload.","schema":{"type":"string","example":"verifactu.status.updated"}},{"name":"BeeL-Event-Id","in":"header","required":true,"description":"Webhook event UUID — identical across all retries of the same logical event. Matches the `id` field of the payload.","schema":{"type":"string","format":"uuid"}},{"name":"BeeL-Delivery-Id","in":"header","required":true,"description":"Delivery attempt UUID — unique per HTTP call, including retries. Matches the `id` of the delivery log in the dashboard.","schema":{"type":"string","format":"uuid"}},{"name":"Idempotency-Key","in":"header","required":true,"description":"Same UUID as `BeeL-Event-Id`. Lets the receiver deduplicate deliveries automatically.","schema":{"type":"string","format":"uuid"}}]}}}},"responses":{"201":{"headers":{"Location":{"$ref":"#/components/headers/CreatedResourceLocation"}},"description":"Webhook subscription created","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionWithSecret"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/WebhookSubscriptionValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"get":{"tags":["AccountWebhooks"],"summary":"List the webhook subscriptions","description":"Returns the webhook subscriptions of the account in the path, active and inactive alike. Every member of the account sees the same list: who registered a subscription is authorship, not visibility. The signing secrets are never included.","operationId":"listAccountWebhookSubscriptions","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"}],"responses":{"200":{"description":"List of webhook subscriptions","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"webhooks":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks/{webhook_id}":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"webhook_id","in":"path","required":true,"description":"Subscription of the account in the path. A subscription of another account answers `404`, the same as one that does not exist: under the account resolved from `{account_id}` it simply is not there.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["AccountWebhooks"],"summary":"Get a webhook subscription","description":"Returns a single webhook subscription. The signing secret is never included.","operationId":"getAccountWebhookSubscription","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"responses":{"200":{"description":"Webhook subscription","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscription"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"patch":{"tags":["AccountWebhooks"],"summary":"Update a webhook subscription","description":"Updates the fields present in the body — `url`, `events`, `active`,\n`account_relationship` — and leaves the rest untouched.\n\n- **`events`:** replaces the whole list, it does not add to it, so an event left out\n  of it stops being delivered.\n- **`active`:** setting it to `false` stops deliveries without discarding the delivery\n  history. A subscription we turned off ourselves (`deactivated_by: beel`) needs a\n  successful test delivery before it can be turned back on. Turning a subscription\n  back on counts towards the limit of 10 active subscriptions, and is rejected with\n  `400` `WEBHOOK_ACTIVE_SUBSCRIPTION_LIMIT_REACHED` when ten are already active.\n- **Signing secret:** not touched here. Rotate it with\n  `POST /v1/accounts/{account_id}/webhooks/{webhook_id}/secret`.\n","operationId":"patchAccountWebhookSubscription","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookSubscriptionRequest"},"examples":{"patch_webhook_events":{"$ref":"#/components/examples/patch-events"},"patch_webhook_disable":{"$ref":"#/components/examples/patch-disable"}}}}},"responses":{"200":{"description":"Webhook subscription updated","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscription"}}}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/WebhookSubscriptionValidationError"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}},"delete":{"tags":["AccountWebhooks"],"summary":"Delete a webhook subscription","description":"Permanently deletes a webhook subscription. No further events are\ndelivered to its URL. To stop deliveries reversibly, set `active` to\n`false` instead.\n","operationId":"deleteAccountWebhookSubscription","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"responses":{"204":{"description":"Subscription deleted"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"webhook_id","in":"path","required":true,"description":"Subscription of the account in the path. A subscription of another account answers `404`, the same as one that does not exist: under the account resolved from `{account_id}` it simply is not there.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["AccountWebhooks"],"summary":"List the delivery attempts of a webhook","description":"Returns the delivery attempts of this subscription, newest first. Each entry records\none attempt with the response it got, so a retried event appears once per attempt.\n\n- **`event_type`:** narrows the list to a single event type.\n- **`event_id`:** follows one event across every attempt made on it, without paging\n  through the whole history.\n","operationId":"listAccountWebhookDeliveries","x-required-scopes":["webhooks:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"event_type","in":"query","required":false,"description":"Only deliveries of this event type.","schema":{"type":"string"},"example":"invoice.issued"},{"name":"event_id","in":"query","required":false,"description":"Only deliveries of this event. Use it to follow every attempt on one event without paging through the whole history.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Delivery attempts, newest first.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"deliveries":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDeliveryLog"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks/{webhook_id}/deliveries/{delivery_id}/retry":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"webhook_id","in":"path","required":true,"description":"Subscription of the account in the path. A subscription of another account answers `404`, the same as one that does not exist: under the account resolved from `{account_id}` it simply is not there.","schema":{"type":"string","format":"uuid"}},{"name":"delivery_id","in":"path","required":true,"description":"Delivery attempt of that subscription to replay.","schema":{"type":"string","format":"uuid"}}],"post":{"tags":["AccountWebhooks"],"summary":"Retry a failed webhook delivery","description":"Re-sends the original payload of a delivery immediately.\n\n- **Payload:** the one captured when the event happened, not a fresh snapshot, so\n  changes made to the entity since then are not reflected.\n- **History:** the outcome is recorded as a new entry and the original entry is kept\n  as it was. `attempt_number` continues the same sequence, so it can exceed the number\n  of automatic attempts.\n","operationId":"retryAccountWebhookDelivery","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Retry result","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookDeliveryLog"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks/{webhook_id}/test":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"webhook_id","in":"path","required":true,"description":"Subscription of the account in the path. A subscription of another account answers `404`, the same as one that does not exist: under the account resolved from `{account_id}` it simply is not there.","schema":{"type":"string","format":"uuid"}}],"post":{"tags":["AccountWebhooks"],"summary":"Send a test event to a webhook endpoint","description":"Sends a synthetic payload to the subscription's URL immediately, outside the normal\ndelivery queue. Use it to verify that your endpoint is reachable and handles\ndeliveries correctly before you rely on real events.\n\n- **Payload:** carries `\"test\": true` and synthetic data, and is signed like any other\n  delivery, so it also exercises your signature check.\n- **Retries:** none. A failed test is not retried and does not appear in the delivery\n  history.\n- **`Idempotency-Key`:** repeating the call with the same key returns the cached\n  result without sending the test payload again.\n- **Result:** read `delivery_success`; a delivery your endpoint rejected is still a\n  successful test run, not an error.\n","operationId":"testAccountWebhookSubscription","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"The test was attempted. Check `delivery_success` for the outcome: a delivery your\nendpoint rejected is still a successful test run, not an error.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookTestResult"}}}]},"examples":{"success":{"summary":"Endpoint responded with 2xx","value":{"success":true,"data":{"delivery_success":true,"http_status":200,"duration_ms":142,"error":null}}},"failure":{"summary":"Endpoint returned an error","value":{"success":true,"data":{"delivery_success":false,"http_status":503,"duration_ms":318,"error":"HTTP 503","failure_cause":"http_server_error"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/webhooks/{webhook_id}/secret":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential, and not the `BeeL-Active-Company` header — decides which account the operation acts on. An account you do not reach answers `403`, and so does an account that does not exist, so the existence of somebody else's account is never disclosed.","schema":{"type":"string","format":"uuid"}},{"name":"webhook_id","in":"path","required":true,"description":"Subscription of the account in the path. A subscription of another account answers `404`, the same as one that does not exist: under the account resolved from `{account_id}` it simply is not there.","schema":{"type":"string","format":"uuid"}}],"post":{"tags":["AccountWebhooks"],"summary":"Rotate the signing secret of a webhook","description":"Generates a new HMAC signing secret for a webhook subscription.\n\n- **Old secret:** **immediately invalidated**. Update your signature verification\n  logic before rotating, to avoid missing events during the transition.\n- **New secret:** returned **once**, in this response only. It cannot be read again.\n","operationId":"rotateAccountWebhookSecret","x-required-scopes":["webhooks:write"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["webhooks:write"]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKeyHeader"}],"responses":{"200":{"description":"Secret rotated — new secret returned (only shown once)","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/WebhookSubscriptionWithSecret"}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/request-logs":{"get":{"tags":["AccountRequestLogs"],"summary":"List API request logs","description":"Returns the history of public API requests made by you, with any of your API keys in this\nenvironment — not only the key you are authenticating with. Only `auth_type=API_KEY`\ntraffic is recorded.\n\n- **The axis is the person, not the individual credential:** a second key of yours sees the\n  same history, and narrowing it to one key is a filter (`api_key_id`), not the default.\n- **It is still not the account's traffic:** requests made by other users of the same\n  account, or by their API keys, are never returned. The `{account_id}` in the path\n  authorizes the call; it does not widen what you can see.\n- **Environment is not a filter:** results are always scoped to the environment of the\n  credential you authenticate with — a `beel_sk_test_*` key sees the test traffic of all\n  your test keys, a `beel_sk_live_*` key the live traffic of all your live ones. To see\n  the other environment, use a key from that environment.\n- **Cursor pagination:** navigate with the opaque `cursor` returned in `next_cursor` /\n  `prev_cursor`; there is no jump to an arbitrary page N.\n- **Time window:** defaults to the last 30 days; narrow or move it with `from`/`to`.\n","operationId":"listAccountRequestLogs","x-required-scopes":["logs:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["logs:read"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account the call is authorized against. It does not widen the result set.","schema":{"type":"string","format":"uuid"}},{"name":"only_errors","in":"query","required":false,"description":"If true, only requests with status >= 400.","schema":{"type":"boolean","default":false}},{"name":"method","in":"query","required":false,"description":"Filter by HTTP method.","schema":{"type":"string","example":"POST"}},{"name":"http_status","in":"query","required":false,"description":"Filter by an exact HTTP status code.","schema":{"type":"integer","minimum":100,"maximum":599}},{"name":"path_contains","in":"query","required":false,"description":"Case-insensitive substring of the actual request path, IDs included (for example `/v1/companies/3f1c9a2e-5b7d-4e8f-9a1b-2c3d4e5f6a7b/invoices/8a2b4c6d-1e3f-4a5b-8c7d-9e0f1a2b3c4d/send`), so a path template with `{placeholders}` never matches, and a full path only matches requests made to that exact route shape. Prefer a resource segment such as `/invoices`, which matches that resource on every route that serves it.","schema":{"type":"string","example":"/invoices"}},{"name":"api_key_id","in":"query","required":false,"description":"Narrow the result to one of your API keys. Any key of yours in this environment is accepted, not just the one you authenticate with; a key belonging to someone else simply yields no results.","schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":false,"description":"Lower bound of the time range (inclusive). Defaults to 30 days ago.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Upper bound of the time range (inclusive). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor returned by a previous response (next_cursor / prev_cursor).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Maximum number of log entries in the page, from 1 to 100. Defaults to 25.","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Logs retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableRequestLogQuery"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/request-logs/{request_id}":{"get":{"tags":["AccountRequestLogs"],"summary":"Get an API request log detail","description":"Returns the full detail (bodies and headers) of a request made by you, with any of your API\nkeys in this environment — including one made with a key other than the one you are\nauthenticating with, because the axis is the person, not the individual credential.\n\n- **`{account_id}`:** authorizes the call; it does not widen what you can see.\n- **`404`:** the request does not exist, was made by another user (including another user\n  of this same account), or belongs to the other environment.\n- **The widest read `logs:read` opens:** it returns the bodies and headers that any key\n  of yours exchanged in this environment, so a key holding only `logs:read` reads the\n  traffic of your privileged keys too. It never crosses to another user or to another\n  account. Grant it accordingly.\n","operationId":"getAccountRequestLog","x-required-scopes":["logs:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["logs:read"]}],"parameters":[{"name":"account_id","in":"path","required":true,"description":"Account the call is authorized against. It does not widen the result set.","schema":{"type":"string","format":"uuid"}},{"name":"request_id","in":"path","required":true,"description":"Correlation identifier (X-Request-Id).","schema":{"type":"string"}},{"name":"timestamp","in":"query","required":false,"description":"Log timestamp (the one returned by the list). Narrows the search window around\nthat instant so the detail also works for logs older than the default window.\nIf omitted, the default recent window is searched.\n","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Detail retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogSingleResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/RequestLogNotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/developers/request-logs":{"get":{"tags":["RequestLogs"],"summary":"List API request logs","description":"Returns the history of public API requests made by you, with any of your API keys in this\nenvironment — not only the key you are authenticating with. Only `auth_type=API_KEY`\ntraffic is recorded.\n\n- **Deprecated:** use `GET /v1/accounts/{account_id}/request-logs`, whose result set is\n  identical.\n\n## Whose traffic you see\n\n- **The axis is the person, not the individual credential:** a second key of yours sees the\n  same history, and narrowing it to one key is a filter (`api_key_id`), not the default.\n  Requests made by other users, including other users of the same account, are never\n  returned.\n- **Environment is not a filter:** results are always scoped to the environment of the\n  credential you authenticate with — a `beel_sk_test_*` key sees the test traffic of all\n  your test keys, a `beel_sk_live_*` key the live traffic of all your live ones. To see\n  the other environment, use a key from that environment.\n\n## Browsing the history\n\n- **Cursor pagination:** navigate with the opaque `cursor` returned in `next_cursor` /\n  `prev_cursor`; there is no jump to an arbitrary page N.\n- **Time window:** defaults to the last 30 days; narrow or move it with `from`/`to`.\n\n**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.\n","operationId":"listDeveloperRequestLogs","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/request-logs"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["logs:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["logs:read"]}],"parameters":[{"name":"only_errors","in":"query","required":false,"description":"If true, only requests with status >= 400.","schema":{"type":"boolean","default":false}},{"name":"method","in":"query","required":false,"description":"Filter by HTTP method.","schema":{"type":"string","example":"POST"}},{"name":"http_status","in":"query","required":false,"description":"Filter by an exact HTTP status code.","schema":{"type":"integer","minimum":100,"maximum":599}},{"name":"path_contains","in":"query","required":false,"description":"Case-insensitive substring of the actual request path, IDs included (for example `/v1/companies/3f1c9a2e-5b7d-4e8f-9a1b-2c3d4e5f6a7b/invoices/8a2b4c6d-1e3f-4a5b-8c7d-9e0f1a2b3c4d/send`), so a path template with `{placeholders}` never matches, and a full path only matches requests made to that exact route shape. Prefer a resource segment such as `/invoices`, which matches that resource on every route that serves it.","schema":{"type":"string","example":"/invoices"}},{"name":"api_key_id","in":"query","required":false,"description":"Filter by a specific API key.","schema":{"type":"string","format":"uuid"}},{"name":"from","in":"query","required":false,"description":"Lower bound of the time range (inclusive). Defaults to 30 days ago.","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","required":false,"description":"Upper bound of the time range (inclusive). Defaults to now.","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"Opaque cursor returned by a previous response (next_cursor / prev_cursor).","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Logs retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogListResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableRequestLogQuery"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/developers/request-logs/{request_id}":{"get":{"tags":["RequestLogs"],"summary":"Get an API request log detail","description":"Returns the full detail (bodies and headers) of a request made by you, with any of your API\nkeys in this environment — the axis is the person, not the individual credential.\n\n- **Deprecated:** use `GET /v1/accounts/{account_id}/request-logs/{request_id}`, whose\n  result is identical.\n- **`404`:** the request does not exist, was made by another user, or belongs to the other\n  environment.\n- **The widest read `logs:read` opens:** it returns the bodies and headers that any key\n  of yours exchanged in this environment, so a key holding only `logs:read` reads the\n  traffic of your privileged keys too. It never crosses to another user or to another\n  account. Grant it accordingly.\n\n**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.\n","operationId":"getDeveloperRequestLog","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/request-logs/{request_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["logs:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["logs:read"]}],"parameters":[{"name":"request_id","in":"path","required":true,"description":"Correlation identifier (X-Request-Id).","schema":{"type":"string"}},{"name":"timestamp","in":"query","required":false,"description":"Log timestamp (the one returned by the list). Narrows the search window around\nthat instant so the detail also works for logs older than the default window.\nIf omitted, the default recent window is searched.\n","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Detail retrieved successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLogSingleResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/RequestLogNotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/emails":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["AccountEmails"],"summary":"List the email history","description":"Returns the emails the system recorded on behalf of the account in the path: invoice\ndeliveries, verification, onboarding. It only reads the history; it does not send or resend\nanything.\n\n- **Every attempt is recorded**, not only the ones that went out: an email stopped by\n  policy is listed with `status` `REJECTED`, and one accepted but not dispatched yet as\n  `QUEUED`, rather than being omitted.\n- **Order:** by `sent_at` descending, configurable with `sort_by` / `sort_order`.\n- **Filters:** `type`, `status`, `recipient` and `related_entity_id`.\n- **`sent_at`:** the moment the message was handed over, so it is absent while an email is\n  still `QUEUED`.\n- **Scope:** the account is the one named in the path; the environment is not, and comes\n  from the credential.\n","operationId":"listAccountEmailDeliveries","x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by email type (e.g. INVOICE_EMITTED)","example":"INVOICE_EMITTED"},{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"description":"Filter by delivery status"},{"name":"recipient","in":"query","required":false,"schema":{"type":"string"},"description":"Filter to emails where any recipient contains the term (case-insensitive)","example":"cliente@ejemplo.com"},{"name":"related_entity_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Filter to emails associated with a given related entity (e.g. an invoice id)","example":"660e8400-e29b-41d4-a716-446655440111"},{"name":"sort_by","in":"query","required":false,"description":"Field to sort by","schema":{"allOf":[{"$ref":"#/components/schemas/EmailDeliverySortBy"}],"default":"sent_at"}},{"name":"sort_order","in":"query","required":false,"description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"desc"}}],"responses":{"200":{"description":"Email history retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryListResponse"},"example":{"success":true,"data":{"emails":[{"id":"550e8400-e29b-41d4-a716-446655440000","email_type":"INVOICE_EMITTED","recipients":["cliente@ejemplo.com"],"cc":[],"subject":"Factura 2025/0001","related_entity_type":"INVOICE","related_entity_id":"660e8400-e29b-41d4-a716-446655440111","status":"SENT","sent_at":"2025-01-20T14:30:00Z"}],"pagination":{"current_page":1,"items_per_page":20,"total_items":1,"total_pages":1,"has_next":false,"has_previous":false}},"meta":{"timestamp":"2025-01-20T15:00:00Z","request_id":"req_123"}}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential cannot reach the account in the path, or lacks the `emails:read` scope.\nAn account you cannot reach and an account that does not exist answer identically.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/email-indicators":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["AccountEmails"],"summary":"Get the email indicators of several entities","description":"Returns, for each related entity id given, how many emails the history holds for it, the\nstatus of the most recent one and when it was sent. Lets you show the state of an entity's\nemail without loading its full history.\n\n- **`last_status`:** carries whatever the latest attempt ended in, `REJECTED` and `QUEUED`\n  included, so a `count` above zero does not mean an email reached anyone.\n- **Ids with no associated emails:** omitted from the response rather than returned with\n  `count` 0.\n\n**Closed catalogue.** This collection is fixed and bounded by the request itself — at most\none indicator per id in `related_entity_ids`: it carries no `pagination`, it takes no\n`page`/`limit`, and every response holds the whole set.\n","operationId":"getAccountEmailDeliveryIndicators","x-closed-catalogue":true,"x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"name":"related_entity_ids","in":"query","required":true,"description":"Comma-separated list of related entity ids (e.g. invoice ids)","schema":{"type":"array","items":{"type":"string","format":"uuid"}},"style":"form","explode":false}],"responses":{"200":{"description":"Indicators retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryIndicatorListResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential cannot reach the account in the path, or lacks the `emails:read` scope.\nAn account you cannot reach and an account that does not exist answer identically.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/accounts/{account_id}/emails/{email_id}":{"parameters":[{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}}],"get":{"tags":["AccountEmails"],"summary":"Get an email of the history","description":"Returns one recorded email with its message body (HTML and plain text), its attachments\nand, for batch emails, the invoices it carried.\n\n- **`body_available`:** the body is fetched live and is only available while the message\n  has a provider message id and the provider still retains it; otherwise it is `false` and\n  `html_body` / `text_body` are `null`.\n- **An email that never left:** `QUEUED` or `REJECTED`, it has no body for that reason.\n","operationId":"getAccountEmailDelivery","x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"name":"email_id","in":"path","required":true,"description":"Email delivery id","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Email retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryDetailResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"The credential cannot reach the account in the path, or lacks the `emails:read` scope.\nAn account you cannot reach and an account that does not exist answer identically.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/emails":{"get":{"tags":["Emails"],"summary":"List the email history","description":"Returns the emails the system recorded on behalf of the authenticated account: invoice\ndeliveries, verification, onboarding. It only reads the history; it does not send or resend\nanything.\n\n- **Deprecated:** use `GET /v1/accounts/{account_id}/emails`, which behaves identically.\n\n- **Every attempt is recorded**, not only the ones that went out: an email stopped by\n  policy is listed with `status` `REJECTED`, and one accepted but not dispatched yet as\n  `QUEUED`, rather than being omitted.\n- **Order:** by `sent_at` descending, configurable with `sort_by` / `sort_order`.\n- **Filters:** `type`, `status`, `recipient` and `related_entity_id`.\n- **`sent_at`:** the moment the message was handed over, so it is absent while an email is\n  still `QUEUED`.\n\n**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.\n","operationId":"listEmailDeliveries","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/emails"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/LimitParam"},{"name":"type","in":"query","required":false,"schema":{"type":"string"},"description":"Filter by email type (e.g. INVOICE_EMITTED)","example":"INVOICE_EMITTED"},{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"description":"Filter by delivery status"},{"name":"recipient","in":"query","required":false,"schema":{"type":"string"},"description":"Filter to emails where any recipient contains the term (case-insensitive)","example":"cliente@ejemplo.com"},{"name":"related_entity_id","in":"query","required":false,"schema":{"type":"string","format":"uuid"},"description":"Filter to emails associated with a given related entity (e.g. an invoice id)","example":"660e8400-e29b-41d4-a716-446655440111"},{"name":"sort_by","in":"query","required":false,"description":"Field to sort by","schema":{"allOf":[{"$ref":"#/components/schemas/EmailDeliverySortBy"}],"default":"sent_at"}},{"name":"sort_order","in":"query","required":false,"description":"Sort order direction","schema":{"allOf":[{"$ref":"#/components/schemas/SortOrder"}],"default":"desc"}}],"responses":{"200":{"description":"Email history retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryListResponse"},"example":{"success":true,"data":{"emails":[{"id":"550e8400-e29b-41d4-a716-446655440000","email_type":"INVOICE_EMITTED","recipients":["cliente@ejemplo.com"],"cc":[],"subject":"Factura 2025/0001","related_entity_type":"INVOICE","related_entity_id":"660e8400-e29b-41d4-a716-446655440111","status":"SENT","sent_at":"2025-01-20T14:30:00Z"}],"pagination":{"current_page":1,"items_per_page":20,"total_items":1,"total_pages":1,"has_next":false,"has_previous":false}},"meta":{"timestamp":"2025-01-20T15:00:00Z","request_id":"req_123"}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/UnprocessableQueryParameter"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/emails/indicators":{"get":{"tags":["Emails"],"summary":"Get the email indicators of several entities","description":"Returns, for each related entity id given, how many emails the history holds for it, the\nstatus of the most recent one and when it was sent. Lets you show the state of an entity's\nemail without loading its full history.\n\n- **`last_status`:** carries whatever the latest attempt ended in, `REJECTED` and `QUEUED`\n  included, so a `count` above zero does not mean an email reached anyone.\n- **Ids with no associated emails:** omitted from the response rather than returned with\n  `count` 0.\n- **Deprecated:** use `GET /v1/accounts/{account_id}/email-indicators`, which behaves\n  identically.  The successor is a sibling of the email collection, not `…/emails/indicators`:\n  `indicators` used to sit where an email id goes.\n\n**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.\n","operationId":"getEmailDeliveryIndicators","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/email-indicators"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"name":"related_entity_ids","in":"query","required":true,"description":"Comma-separated list of related entity ids (e.g. invoice ids)","schema":{"type":"array","items":{"type":"string","format":"uuid"}},"style":"form","explode":false}],"responses":{"200":{"description":"Indicators retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryIndicatorListResponse"}}}},"400":{"$ref":"#/components/responses/InvalidRequestTarget"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}},"/v1/emails/{email_id}":{"get":{"tags":["Emails"],"summary":"Get an email of the history","description":"Returns one recorded email with its message body (HTML and plain text), its attachments\nand, for batch emails, the invoices it carried.\n\n- **`body_available`:** the body is fetched live and is only available while the message\n  has a provider message id and the provider still retains it; otherwise it is `false` and\n  `html_body` / `text_body` are `null`.\n- **An email that never left:** `QUEUED` or `REJECTED`, it has no body for that reason.\n- **Deprecated:** use `GET /v1/accounts/{account_id}/emails/{email_id}`, which behaves\n  identically.\n\n**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.\n","operationId":"getEmailDelivery","x-successor":{"method":"GET","path":"/v1/accounts/{account_id}/emails/{email_id}"},"deprecated":true,"x-deprecation":"nif-migration","x-required-scopes":["emails:read"],"security":[{"ApiKeyAuth":[]},{"OAuth2":["emails:read"]}],"parameters":[{"name":"email_id","in":"path","required":true,"description":"Email delivery id","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Email retrieved successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailDeliveryDetailResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"},"default":{"$ref":"#/components/responses/UnexpectedError"}}}}},"components":{"parameters":{"IdempotencyKeyHeader":{"name":"Idempotency-Key","in":"header","description":"Idempotency key to prevent duplicates in sensitive operations.\n\n- Any unique client-generated string (e.g. an order id). A UUID also works but is not required\n- Allowed characters: letters, digits, `_` and `-` (max 255 chars)\n- Retrying with the same key replays the first response when it was a success (2xx) or a\n  server error (5xx): same status and body, plus the header `Idempotency-Replay: true`.\n  After a 5xx, check whether the operation took effect before retrying with a **new** key\n- A 4xx is not stored: the key is released, so the corrected request can reuse it\n- Stored responses expire 24 hours after processing\n\nThe key is scoped per user and environment, and bound to the request body, so retrying after\na network timeout replays the stored response instead of repeating the operation.\n\n| Status | Code | When |\n|---|---|---|\n| `400` | `INVALID_IDEMPOTENCY_KEY` | The key breaks the format rules above. |\n| `409` | `IDEMPOTENCY_KEY_PROCESSING` | The first request is still in flight. Wait for the `Retry-After` seconds (2) and retry with the same key. |\n| `409` | `IDEMPOTENCY_KEY_MISMATCH` | The key was already used with a **different** body. Use a new key. |\n","required":false,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":255},"example":"invoice-order-12345"},"ActiveCompanyHeader":{"name":"BeeL-Active-Company","in":"header","description":"Which company (tax ID) the request operates on.\n\nAn API key belongs to an account, and an account may hold several companies. Endpoints that\nread or write company-owned data — invoices, customers, products, series, tax and VeriFactu\nsettings — resolve their target company from this header **when the path does not already\nname one**.\n\nOn a path that names the company, such as `/v1/companies/{company_id}/invoices`, the path\nis the target and this header is not read at all: it neither switches the target nor makes\nthe request fail, so sending one that disagrees with the path is silently ignored rather\nthan rejected. Prefer those paths whenever you need to be explicit about which tax ID you\nare operating on.\n\nOn an account holding a single company the header may be omitted — that company is used.\nOn an account holding several it is required: the request fails with\n`403 ACTIVE_COMPANY_REQUIRED` otherwise. A company-owned record always belongs to one tax\nID, so operating on it without saying which one has no meaning.\n\nThe company does **not** have to belong to the API key's own account: one you manage works\ntoo, which is what makes the `company_id` returned by `POST /v1/accounts` usable here to\ninvoice on a provisioned account's behalf. What you may do with it is then decided by your\naccess level over that account, and issuing also requires a signed fiscal representation.\n\nThis header is how you **operate** on a company, never how you find one: a value you do not\nreach answers `403`, the same as one that does not exist, so guessing reveals nothing. Get\nthe `company_id` from the response that created the company.\n\nRequires the `companies:read` scope. Returns `403` if the company is neither yours nor one\nyou manage.\n","required":false,"schema":{"type":"string","format":"uuid"},"example":"550e8400-e29b-41d4-a716-446655440000"},"PageParam":{"name":"page","in":"query","description":"Page number, starting at 1. The response echoes it back as `pagination.current_page`.","schema":{"type":"integer","minimum":1,"default":1},"example":1},"LimitParam":{"name":"limit","in":"query","description":"How many items to return per page. The response echoes it back as `pagination.items_per_page`.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20},"example":20},"RequiredIdempotencyKeyHeader":{"name":"Idempotency-Key","in":"header","description":"Same key as `Idempotency-Key` above, but **required**: the operation writes many rows per\ncall, so a retry without a key would import the same file twice. A missing key answers\n`400 IDEMPOTENCY_KEY_REQUIRED`.\n","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]+$","maxLength":255},"example":"customer-import-2026-03-01"},"AccountIdPath":{"name":"account_id","in":"path","required":true,"description":"Your own account, or an account you provisioned. It — not the credential — decides which account the operation acts on; a `403` is returned when you do not reach it, the same response an account that does not exist gets.","schema":{"type":"string","format":"uuid"}},"MemberIdPath":{"name":"member_id","in":"path","required":true,"description":"Membership unique UUID.","schema":{"type":"string","format":"uuid"}},"PaymentConnectionIdParam":{"name":"connection_id","in":"path","required":true,"description":"Unique identifier (UUID) of the payment connection the operation acts on, as returned by `GET /v1/companies/{company_id}/payment-connections`. A NIF can hold several connections of the same provider, so the provider slug alone does not name one. A connection of another NIF answers `404`, exactly like one that does not exist.","schema":{"type":"string","format":"uuid"}},"CompanyIdParam":{"name":"company_id","in":"path","required":true,"description":"Unique identifier (UUID) of the company the events belong to — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.","schema":{"type":"string","format":"uuid"}},"EventIdParam":{"name":"event_id","in":"path","required":true,"description":"Identifier of the payment event, as returned by the list operation.","schema":{"type":"string","format":"uuid"}}},"schemas":{"SuccessResponse":{"type":"object","description":"The envelope every successful JSON response of the BeeL. API is wrapped in. There are no\nbare resources in v1 and none are planned: the payload always hangs off `data`.\n\n- A **single resource** is an object in `data`.\n- A **collection** hangs off a named key inside `data`, together with its `pagination`,\n  also inside `data` — `data: {invoices: [...], pagination: {...}}`.\n- A collection carries `pagination` unless its operation declares itself a **closed\n  catalogue**: a fixed, bounded list with nothing to page through. The declaration is\n  explicit in the operation; a missing `pagination` is never something to infer.\n- `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a\n  documented variant of pagination, not another envelope.\n\nPutting the array straight into `data` with `pagination` as its sibling is the shape a v2\nwould adopt; v1 is not being flipped to it.\n","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"description":"The payload. An object for a single resource; an object holding the named collection\n(and its `pagination`) for a listing. Never a bare array at this level in v1.\n"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"ErrorResponse":{"type":"object","description":"Error response shared by all BeeL. APIs.\n\nThe payload carries **two contracts at once** (additive, non-breaking):\n\n- **Legacy** (`success`, `error.{code,message,details}`, `meta`) — kept\n  intact for existing consumers.\n- **RFC 9457** (`type`, `title`, `detail`, `instance`) — new fields\n  for integrators following Problem Details for HTTP APIs. The\n  `type` URI is the stable, shareable link to the error's\n  documentation page (e.g. `https://docs.beel.es/errors/{code}`).\n\nFuture migration: the legacy fields will be deprecated via\n`Deprecation`/`Sunset` headers after a sufficient adoption window,\nand the response Content-Type will move to\n`application/problem+json`.\n","required":["success","error"],"properties":{"success":{"type":"boolean","example":false},"error":{"$ref":"#/components/schemas/ErrorDetail"},"meta":{"$ref":"#/components/schemas/ResponseMeta"},"type":{"type":"string","format":"uri","description":"Stable URI that identifies the problem type and where the\nintegrator will find its documentation.\n","example":"https://docs.beel.es/errors/INVOICE_NO_LINES"},"title":{"type":"string","description":"Short summary of the problem type. Stable between occurrences\nof the same `type`.\n","example":"INVOICE_NO_LINES"},"detail":{"type":"string","description":"Specific message for this occurrence, localized according to\n`Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy\n`error.message`.\n","example":"The invoice must have at least one line"},"instance":{"type":"string","format":"uri-reference","description":"URI that identifies the specific occurrence of the problem —\ntypically the path of the affected resource.\n","example":"/v1/invoices/abc-123"}}},"ErrorDetail":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string","example":"The provided data is not valid"},"details":{"type":"object","additionalProperties":true,"example":{"field":"specific error message"}}}},"LineUnitPriceOutOfRangeDetails":{"type":"object","description":"Machine-readable context for `LINE_UNIT_PRICE_OUT_OF_RANGE` on a line priced by declared\ntotal (`total_excluding_tax` or `total_including_tax`). On those lines the unit price is\nnot sent: it is derived as total ÷ quantity, and this error says the result does not fit\nthe field that stores it.\n\n`field` names the **line**, not a unit price field: `unit_price` is absent from a line\npriced by total, and neither the total nor the quantity is out of range on its own — the\nquotient is. Fix it by lowering the total or raising the quantity until\n`derived_unit_price` is at most `max`.\n\nThe two amounts travel as JSON numbers so the value can be compared against the limit\nwithout parsing `error.message`, which is translated to three languages.\n","required":["field","max","derived_unit_price"],"properties":{"field":{"type":"string","description":"The line the quotient came from, by its position in the request's `lines` array\n(`lines[0]` is the first line). Just `lines` when the line carries no position yet.\n","example":"lines[0]"},"max":{"type":"number","description":"Largest unit price that can be stored, per unit.","example":99999999.9999},"derived_unit_price":{"type":"number","description":"The quotient of total ÷ quantity that was rejected, computed at 4 decimal places.\nIt is a JSON number, so trailing zeros are not preserved: a quotient computed as\n9999999999.0000 is published as `9999999999`.\n","example":9999999999}}},"CorrectiveInvoiceErrorDetails":{"type":"object","description":"Machine-readable context for the errors that refuse a corrective invoice because of what it\nrectifies or when. Every field is optional: each `error.code` fills the ones listed next to\nit and leaves the rest absent, so the limit can be read without parsing `error.message`.\n","properties":{"tax_group":{"type":"string","description":"The rate whose taxable base the corrective would take below zero: the tax, its rate and,\nwhen there is one, the equivalence surcharge (`IVA 21%`, `IVA 21% + RE 5.2%`), or\n`SUPLIDO` for disbursements (`CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`).\n","example":"IVA 21%"},"max_reduction":{"type":"string","description":"How much of that rate is still invoiced and can be rectified downwards, in euros with\ntwo decimals: the original plus the correctives already issued against it\n(`CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`).\n","example":"700.00"},"deadline":{"type":"string","format":"date","description":"The last day on which the corrective could have been issued: four years after\n`counted_from` (`CORRECTIVE_OUT_OF_TIME`).\n","example":"2025-03-10"},"counted_from":{"type":"string","format":"date","description":"The date a period counts from. For `CORRECTIVE_OUT_OF_TIME`, the original's operation\ndate (its `operation_date`, or its `issue_date` when it has none) or the\n`circumstance_date` of an article 80 cause when one was sent. For\n`CORRECTIVE_BAD_DEBT_TOO_EARLY`, the original's operation date.\n","example":"2021-03-10"},"earliest_date":{"type":"string","format":"date","description":"The first day on which a bad-debt corrective (`R3`) is possible: six months after the\noriginal's operation date, which is `counted_from` (`CORRECTIVE_BAD_DEBT_TOO_EARLY`).\nOne year applies instead when the previous year's turnover exceeded 6,010,121.04 €.\n","example":"2026-09-10"}}},"ResponseMeta":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time","example":"2025-01-15T10:30:00Z"},"request_id":{"type":"string","example":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"Pagination":{"type":"object","required":["current_page","total_pages","total_items","items_per_page"],"properties":{"current_page":{"type":"integer","example":1},"total_pages":{"type":"integer","description":"How many pages the query has. An empty collection has **one** page — the\nfirst one, empty — so the smallest value a canonical route returns is `1`.\n\nThe deprecated flat aliases (`/v1/invoices`, `/v1/customers`, …) answer `0`\nfor that same empty collection, and `has_next`/`has_previous` follow suit.\nThey were left as they were on purpose: a client that reads `total_pages == 0`\nas \"no results\" would break if it changed under it. Do not compare the two\nfamilies of routes field by field; read `total_items` if what you want to know\nis whether anything came back.\n","example":5},"total_items":{"type":"integer","example":87},"items_per_page":{"type":"integer","example":20},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}}},"LegalRepresentative":{"type":"object","required":["full_name","nif","address"],"description":"Legal representative data for a legal entity.\nOnly used when entity_type = LEGAL_ENTITY.\n","properties":{"full_name":{"type":"string","minLength":1,"maxLength":255,"description":"Full name of the legal representative","example":"María García López"},"nif":{"type":"string","pattern":"^(\\d{8}[A-Z]|[ABCDEFGHJKLMNPQRSUVW]\\d{7}[A-Z0-9]|[XYZ]\\d{7}[A-Z])$","minLength":9,"maxLength":9,"description":"Tax ID of the legal representative (DNI/CIF/NIE)","example":"12345678A"},"address":{"allOf":[{"$ref":"#/components/schemas/Address"},{"description":"Address of the legal representative"}]}}},"LegalRepresentativeResponse":{"type":"object","required":["full_name","nif","address"],"description":"Legal representative of a legal entity, as stored.\nOnly present when entity_type = LEGAL_ENTITY.\n","properties":{"full_name":{"type":"string","minLength":1,"maxLength":255,"description":"Full name of the legal representative","example":"María García López"},"nif":{"type":"string","minLength":9,"maxLength":9,"description":"Tax ID of the legal representative (DNI/CIF/NIE)","example":"12345678A"},"address":{"allOf":[{"$ref":"#/components/schemas/AddressResponse"},{"description":"Address of the legal representative"}]}}},"Address":{"description":"Address you send when you create or update a company, a customer or an onboarding.\n\nA required field made only of whitespace passes the length checks below but leaves the\naddress without that part, so it is rejected with `422`: `STREET_EMPTY`,\n`POSTAL_CODE_EMPTY`, `CITY_EMPTY` or `PROVINCE_EMPTY`.\n\nAddresses you read back are described by their own schema.\n","type":"object","required":["street","postal_code","city"],"properties":{"street":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[a-zA-Z0-9À-ÿ\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'ºª°:;\"()&#]+$","description":"Full address (street, number, floor, etc.) - Latin characters only","example":"Calle Mayor, 123"},"number":{"type":"string","minLength":1,"maxLength":20,"description":"Street number. Optional: omit it when the address has none, or when `street` already\ncarries the address in full.\n","example":"123"},"floor":{"type":"string","maxLength":10,"description":"Floor or level","example":"2º A"},"door":{"type":"string","maxLength":10,"description":"Door or apartment","example":"A"},"postal_code":{"type":"string","minLength":1,"maxLength":20,"description":"Postal code (5 digits for Spain, free format for other countries)","example":"28001"},"city":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-zA-Z0-9À-ÿ\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'ºª°:;\"()&#]+$","description":"City or town - Latin characters only","example":"Madrid"},"province":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-zA-Z0-9À-ÿ\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'ºª°:;\"()&#]+$","description":"Province or state - Latin characters only. Required for an address in Spain\n(`country_code` `ES`, or no country at all); optional elsewhere, where many addresses\nhave none. A Spanish address without it is rejected with a `422`.\n","example":"Madrid"},"country":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-zA-Z0-9À-ÿ\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'ºª°:;\"()&#]+$","description":"Country of the address, as its ISO 3166-1 alpha-2 code (`GB`) or its official name\nin Spanish, English or Catalan (`Reino Unido`, `United Kingdom`, `Regne Unit`;\ncase and accents are ignored). Anything else, such as `UK`, is rejected with\n`422 COUNTRY_CODE_REQUIRED`: send `country_code` instead. If it names a different\ncountry than `country_code`, `422 COUNTRY_CODE_MISMATCH` (`España` alone yields to\na foreign `country_code`: it was the old default). What is stored and\nreturned is always the Spanish name derived from the resulting code, never the\ntext sent. With neither field present, the address is Spanish (`España`).\n","example":"España"},"country_code":{"type":"string","pattern":"^[A-Z]{2}$","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code: the canonical field that decides the country of\nthe address. It must be a real country code (`GB`, not `UK`); otherwise\n`422 COUNTRY_CODE_REQUIRED`. When it is omitted, the code comes from `country`\n(see there). With neither field present, the address is stored as `ES`.\n","example":"ES"}}},"AddressResponse":{"type":"object","description":"An address as stored. Nothing is guaranteed: the address of a company created by signup\nis empty until onboarding fills it in, so every field may be absent. `Address` (the\nrequest shape) does require them — that is a rule of admission, not a fact about what\nis stored.\n","properties":{"street":{"type":"string","minLength":1,"maxLength":255,"description":"Full address (street, number, floor, etc.)","example":"Calle Mayor, 123"},"number":{"type":"string","minLength":1,"maxLength":20,"description":"Street number. Absent when the stored address has none.","example":"123"},"floor":{"type":"string","maxLength":10,"description":"Floor or level","example":"2º A"},"door":{"type":"string","maxLength":10,"description":"Door or apartment","example":"A"},"postal_code":{"type":"string","minLength":1,"maxLength":20,"description":"Postal code (5 digits for Spain, free format for other countries)","example":"28001"},"city":{"type":"string","minLength":1,"maxLength":100,"description":"City or town","example":"Madrid"},"province":{"type":"string","minLength":1,"maxLength":100,"description":"Province or state. Outside Spain an address may have none.","example":"Madrid"},"country":{"type":"string","minLength":1,"maxLength":100,"description":"Country name: the Spanish name derived from `country_code`. Addresses stored before\nthe country was validated may hold the name as it was sent.\n","example":"España"},"country_code":{"type":"string","pattern":"^[A-Z]{2}$","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 country code","example":"ES"}}},"PaymentMethod":{"type":"string","description":"Payment method shown on invoices and recurring invoices.\n\n- `NONE`: no payment information is shown.\n- `BANK_TRANSFER`: bank transfer to the IBAN of the payment details; the only method\n  that requires an IBAN.\n- `CARD`: card payment.\n- `CASH`: cash payment.\n- `CHECK`: payment by cheque.\n- `DIRECT_DEBIT`: direct debit from the customer's bank account.\n- `BIZUM`: payment through Bizum.\n- `OTHER`: any other method.\n","enum":["NONE","BANK_TRANSFER","CARD","CASH","CHECK","DIRECT_DEBIT","BIZUM","OTHER"],"example":"BANK_TRANSFER"},"EntityType":{"type":"string","description":"Taxpayer type.\nINDIVIDUAL: Natural person (individual self-employed).\nLEGAL_ENTITY: Legal entity (company with legal form: SL, SA, etc.).\n","enum":["INDIVIDUAL","LEGAL_ENTITY"],"example":"INDIVIDUAL"},"Language":{"type":"string","description":"Supported languages: `es` Spanish, `en` English, `ca` Catalan.\n","enum":["es","en","ca"],"example":"es"},"IrpfPercentage":{"type":"number","enum":[0,1,2,2.8,6,7,7.6,9.5,15,19,24],"description":"Withholding (IRPF) percentage, as the IRPF regulation (Royal Decree 439/2007) sets it: 0 (no withholding), 1 (pig fattening and poultry, and some activities\nunder objective estimation), 2 (other agricultural, livestock and forestry activities),\n7 (professional activity in its first three years, and the other 7 % cases), 15\n(professional activities, and intellectual property income), 19 (rent of urban property\nand other income of art. 75.2.b; also the general rate of the Corporate Income Tax\nwithholding) and 24 (image rights). A company that pays Corporate Income Tax can only use\n0, 19, 24 and 9.5: see `WithholdingOptions`.\n\nCeuta and Melilla: income with the Ceuta and Melilla deduction bears the base rate reduced as\nthe law sets it. Under IRPF, 15 % and 7 % (professional activities) and 19 % (rent of urban\nproperty located there) are reduced by 60 %: 6, 2.8 and 7.6. Under Corporate Income Tax, 19 %\non those rents is halved: 9.5, which only a company can use\n(`IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER` otherwise). Whether the reduction applies is the\nissuer's choice: the NIF does not show it.\n\nThe value counts, not how it is written: `15.0` is `15` and `2.80` is `2.8`.\n","example":15},"EquivalenceSurchargePercentage":{"type":"number","enum":[0,0.26,0.5,0.62,1,1.4,1.75,5.2],"description":"Equivalence surcharge percentage in decimal format, one of the values AEAT accepts.\nPairs allowed (VAT rate ↔ surcharge): 21↔5.2, 21↔1.75 (tobacco products), 10↔1.4,\n4↔0.5, and the temporary ones, only on operations of their period: 5↔0.5 up to\n2022-12-31, 5↔0.62 from 2023-01-01 to 2024-09-30, and 7.5↔1 and 2↔0.26 from\n2024-10-01 to 2024-12-31. A pair outside its period is rejected with\n`422 SURCHARGE_RATE_NOT_ACCEPTED_ON_DATE`. `GET /v1/tax-types` publishes every pair with\nits `valid_from` / `valid_until`.\nThe backend automatically normalizes equivalent formats (5.20 → 5.2).\n","example":5.2},"NifValidationStatus":{"type":"string","description":"Tax ID validation status against the AEAT tax registry.\n\n- PENDING: Not yet validated or VeriFactu API temporarily unavailable\n- VALID: Successfully validated against the AEAT tax registry\n- INVALID: Invalid tax ID or does not exist in the AEAT tax registry\n- ERROR: Technical error after exhausting retries\n","enum":["PENDING","VALID","INVALID","ERROR"],"example":"VALID"},"UUID":{"type":"string","format":"uuid","description":"Universally Unique Identifier (UUID v4)","example":"550e8400-e29b-41d4-a716-446655440000"},"NIF":{"type":"string","pattern":"^(\\d{8}[A-Z]|[ABCDEFGHJKLMNPQRSUVW]\\d{7}[A-Z0-9]|[XYZ]\\d{7}[A-Z])$","minLength":9,"maxLength":9,"description":"Spanish Tax ID (9 characters, uppercase only). Structural validation:\n- DNI: 8 digits + 1 letter (e.g., 12345678A)\n- NIE: X/Y/Z + 7 digits + 1 letter (e.g., X1234567A)\n- CIF: Organization letter + 7 digits + 1 control digit/letter (e.g., B12345674)\n","example":"12345678A"},"Email":{"type":"string","format":"email","minLength":5,"maxLength":255,"description":"Email address (minimum valid email is 5 chars, e.g. a@b.co)","example":"user@example.com"},"Phone":{"type":"string","maxLength":20,"description":"A phone number, as the record holds it: digits, spaces, dashes, parentheses and an\noptional leading `+`, up to 20 characters.\n\nThis is the schema a **response** carries, and the length above is the only rule it\nstates. It deliberately does not repeat the character rule, because a number can reach a\nrecord through a path that predates that rule or never passed through this API at all —\na payment provider's customer data, a bulk import. Read the field defensively and do not\nassume it parses.\n\nWhat a **request** has to satisfy is `PhoneInput`, which adds the rules this API enforces\non the way in.\n","example":"+34 612 345 678"},"IBAN":{"type":"string","pattern":"^[A-Z]{2}\\d{2}[A-Z0-9]{1,30}$","minLength":15,"maxLength":34,"description":"IBAN (International Bank Account Number).\nRequired when payment method is BANK_TRANSFER.\n","example":"ES1234567890123456789012"},"SWIFT":{"type":"string","pattern":"^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$","minLength":8,"maxLength":11,"description":"SWIFT/BIC code","example":"ABCDESMMXXX"},"Invoice":{"description":"A stored invoice. Being stored is what makes `id`, `created_at` and `updated_at`\npart of its contract: every one of them always travels.\n","allOf":[{"$ref":"#/components/schemas/InvoiceBase"},{"type":"object","required":["id","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique invoice UUID","example":"550e8400-e29b-41d4-a716-446655440000"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}]},"SeriesInfo":{"type":"object","required":["id","code"],"properties":{"id":{"type":"string","format":"uuid","description":"Invoice series UUID","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"code":{"type":"string","description":"Alphanumeric series code","example":"FAC","pattern":"^[A-Z0-9\\-_]{1,50}$"}}},"InvoiceType":{"type":"string","enum":["STANDARD","CORRECTIVE","SIMPLIFIED","PROFORMA"],"description":"- STANDARD: Standard invoice\n- CORRECTIVE: Corrects or cancels a previous invoice\n- SIMPLIFIED: Simplified invoice (ticket), for a recipient that is not identified. BeeL.\n  requires a STANDARD invoice when the recipient is identified, at any amount: a\n  SIMPLIFIED invoice whose recipient carries an `nif` or `alternative_id` is rejected\n  with `SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`. The only amount BeeL\n  enforces is a cap of 3,000€ VAT included (`SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT`). The\n  general limit of RD 1619/2012 is 400€ (art. 4.1.a); up to 3,000€ applies only to the\n  activities listed in art. 4.2. BeeL does not check which activity the issuer carries\n  out.\n- PROFORMA: Commercial document (formal quote) with no fiscal validity.\n  Never enters VeriFactu (no QR, no AEAT submission): `verifactu.enabled` is\n  always `false`, whatever the company's regime. Requires full recipient data,\n  like STANDARD.\n  Cannot be corrective nor reference a rectified invoice.\n"},"InvoiceStatus":{"type":"string","enum":["SCHEDULED","DRAFT","ISSUED","SENT","PAID","OVERDUE","RECTIFIED","VOIDED","CONVERTED","ACTIVE","EXPIRED"],"description":"- SCHEDULED: Scheduled invoice to be issued automatically on a future date\n- DRAFT: Draft invoice not sent yet (modifiable)\n- ISSUED: Finalized invoice with definitive number but not sent\n- SENT: Invoice sent to customer\n- PAID: Invoice paid\n- OVERDUE: Unpaid invoice in `ISSUED` or `SENT` status, with a total above zero, whose `due_date` is\n  earlier than the current day in Europe/Madrid. A periodic process sets it, so it can lag behind\n  the due date. It changes to `PAID` when the invoice is paid.\n- RECTIFIED: Partially corrected invoice (one or more PARTIAL corrective invoices)\n- VOIDED: Cancelled invoice. Reached either through a direct void request or\n  through a TOTAL corrective invoice; `void_cause` tells the two apart.\n- CONVERTED: Proforma converted into an invoice (terminal; the proforma survives\n  as the record of the accepted quote, linked to the created invoice)\n- ACTIVE: Active proforma. The single working state of a proforma (non-fiscal\n  document): born numbered (PRO-...) and editable, never reaching the fiscal\n  statuses. It transitions to CONVERTED when turned into an invoice, or to VOIDED\n  when the offer is rejected/withdrawn (POST /v1/invoices/{invoice_id}/void).\n- EXPIRED: Proforma whose offer validity (`valid_until`) has passed. Derived on read\n  and never stored; the proforma stays convertible and editable.\n"},"RectificationType":{"type":"string","enum":["TOTAL","PARTIAL"],"description":"Type of rectification applied to a corrective invoice:\n- TOTAL: Rectifies everything still invoiced on the original, its live correctives included (status → VOIDED)\n- PARTIAL: Partially corrects the original invoice (status → RECTIFIED)\n"},"VeriFactuRectificationCode":{"type":"string","enum":["R1","R2","R3","R4","R5"],"description":"Rectification codes according to VeriFactu regulations (AEAT):\n- R1: Error founded in law and Art. 80 One, Two and Six LIVA\n- R2: Article 80 Three LIVA (Bankruptcy proceedings)\n- R3: Article 80 Four LIVA (Uncollectable debts)\n- R4: Other causes\n- R5: Corrective of a simplified invoice - ONLY for simplified invoices\n"},"GenerationAction":{"type":"string","enum":["DRAFT","ISSUE_AND_SEND"],"description":"Action to perform when processing a scheduled invoice:\n- DRAFT: Create as draft for manual review\n- ISSUE_AND_SEND: Issue and send automatically via email\n"},"IssuerData":{"type":"object","required":["legal_name","nif"],"properties":{"legal_name":{"type":"string","minLength":1,"maxLength":255,"description":"Issuer legal name","example":"Juan Pérez García"},"trade_name":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Issuer trade name (optional)","example":"JP Web Development"},"nif":{"type":"string","pattern":"^[A-Za-z0-9]{9}$","minLength":9,"maxLength":9,"description":"Spanish Tax ID (9 alphanumeric characters).\nValid formats:\n- DNI: 8 digits + letter (e.g., 12345678A)\n- NIE: X/Y/Z + 7 digits + letter (e.g., X1234567A)\n- CIF: Letter + 7 digits + digit/letter (e.g., B12345674)\n","example":"12345678A"},"address":{"allOf":[{"$ref":"#/components/schemas/AddressResponse"}],"description":"Issuer address as stored. Optional and absent when the company has not registered its\naddress yet: an address is either complete or it is not there, so no partial address\nand no placeholder is ever returned in its place.\n"},"phone":{"$ref":"#/components/schemas/Phone"},"email":{"$ref":"#/components/schemas/Email"},"website":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Issuer website (optional)","example":"https://beel.es"},"logo_url":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Issuer logo URL (optional)"},"additional_info":{"type":"string","maxLength":1000,"nullable":true,"description":"Additional issuer information (collegiate number, professional registration, etc.)\n","example":"Nº Colegiado: 12345"}}},"RecipientData":{"type":"object","required":["legal_name"],"description":"Recipient data as stored on the invoice. Only `legal_name` is always present; the other\nfields appear when the invoice stores them.\n","properties":{"customer_id":{"type":"string","format":"uuid","nullable":true,"description":"Customer UUID in the system (optional)"},"legal_name":{"type":"string","minLength":1,"maxLength":255,"description":"Recipient legal name","example":"Empresa SL"},"trade_name":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Recipient trade name (optional)","example":"Empresa"},"nif":{"type":"string","pattern":"^[A-Za-z0-9]{9}$","minLength":9,"maxLength":9,"nullable":true,"description":"Spanish Tax ID (9 alphanumeric characters), when the invoice identifies its recipient\nwith one.\nValid formats:\n- DNI: 8 digits + letter (e.g., 12345678A)\n- NIE: X/Y/Z + 7 digits + letter (e.g., X1234567A)\n- CIF: Letter + 7 digits + digit/letter (e.g., B12345674)\n","example":"12345678A"},"alternative_id":{"allOf":[{"$ref":"#/components/schemas/AlternativeIdentifier"},{"description":"Alternative identifier for foreign customers (mutually exclusive with nif), when\nthe invoice identifies its recipient with one.\n"}]},"address":{"allOf":[{"$ref":"#/components/schemas/AddressResponse"}],"description":"Recipient address as stored (optional for simplified invoices). Read shape: an\ninvoice recorded without recipient address still carries the stamped country code,\nso no field is guaranteed.\n"},"phone":{"$ref":"#/components/schemas/Phone"},"email":{"$ref":"#/components/schemas/Email"}}},"InvoiceLine":{"type":"object","required":["quantity","unit_price","line_total"],"properties":{"description":{"type":"string","maxLength":2000,"description":"Description of the invoiced concept. Required for NORMAL lines;\noptional for SUPLIDO lines (may be empty or absent).\n","example":"Web application development"},"quantity":{"type":"number","description":"Product/service quantity (can be negative in corrective invoices)","example":40},"unit":{"type":"string","default":"hours","example":"hours"},"unit_price":{"type":"number","minimum":-99999999.9999,"maximum":99999999.9999,"example":50,"description":"Unit price before taxes (can be negative in corrective invoices).\nSupports up to 4 decimal places for micro-pricing (e.g., €0.0897/unit for labels, packaging).\nFinal amounts are always rounded to 2 decimals.\n\nThis range is wider than the `maximum` the request accepts for `unit_price`, and on\npurpose: on a line priced by declared total the unit price is not sent but derived\n(total ÷ quantity), so what comes back can exceed what you are allowed to send.\n"},"discount_percentage":{"type":"number","minimum":0,"maximum":100,"default":0,"example":10,"description":"Discount percentage applied (0-100)"},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"type":"number","example":5.2,"description":"Equivalence surcharge rate the line was issued with. It is what the invoice holds,\nnot what a request accepts (see `EquivalenceSurchargePercentage`): invoices issued\nwith VAT at 5 % before the surcharge was corrected to 0.62 keep the `0.625` they were\nissued with, because an issued invoice never changes. Their billing record declares\n0.62, as the AEAT information note on the new surcharge rates allows.\n"},"irpf_rate":{"type":"number","example":15,"description":"Withholding (IRPF) rate the line holds. It is what the invoice holds, not what a\nrequest accepts (see `IrpfPercentage`): a line saved with a rate the table no longer\nhas keeps it, and an issued invoice never changes.\n"},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text. Only used when exemption_reason is OTRO."},"taxable_base":{"type":"number","example":1800,"description":"Line taxable base (after discount, can be negative in corrective invoices)"},"line_total":{"type":"number","example":2178,"description":"Line total with taxes (can be negative in corrective invoices)"},"pricing_mode":{"type":"string","enum":["UNIT_PRICE","TOTAL_EXCLUDING_TAX","TOTAL_INCLUDING_TAX"],"default":"UNIT_PRICE","description":"How the line amount was entered. `UNIT_PRICE` = classic mode: the amount is\nderived from `unit_price` (`quantity × unit_price × (1 − discount / 100)`).\n`TOTAL_EXCLUDING_TAX` = total-declared\nmode: `total_excluding_tax` is the exact taxable base and `unit_price`\nis derived and informational (`total / quantity`, 4 decimals).\n`TOTAL_INCLUDING_TAX` = tax-inclusive total-declared mode:\n`total_including_tax` is what the customer paid (taxable base + VAT +\nequivalence surcharge) and the engine works the breakdown backwards so\nthe rounded amounts add up to the declared total exactly.\n"},"total_excluding_tax":{"type":"number","example":1,"description":"Declared line total excluding taxes. Only present on lines with\n`pricing_mode = TOTAL_EXCLUDING_TAX`. Unlike `line_total`, it never\nincludes taxes nor subtracts IRPF withholding.\n"},"total_including_tax":{"type":"number","example":100,"description":"Declared line total including taxes (taxable base + VAT + equivalence\nsurcharge; IRPF withholding is never subtracted). Only present on lines\nwith `pricing_mode = TOTAL_INCLUDING_TAX`. The invariant\n`taxable_base + VAT + surcharge = total_including_tax` holds exactly.\n"},"line_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceLineType"}],"default":"NORMAL","description":"Fiscal line type.\n- **NORMAL**: standard line; contributes to the taxable base and VAT.\n- **SUPLIDO**: payment made on behalf of the final client (art. 78.Tres.3 LIVA);\n  excluded from the taxable base and VAT, and never declared as a line of the\n  VeriFactu record. An invoice made only of SUPLIDO lines is still registered with\n  the AEAT: its record carries a single `S1` line at 0% with base and tax 0, because\n  a record cannot go without a breakdown.\n"},"source_invoice_reference":{"type":"string","nullable":true,"maxLength":50,"description":"Reference to the original invoice issued by the third party in the client's name.\nRequired when line_type=SUPLIDO.\n"},"source_invoice_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Ids of the issued invoices that make up the SUPLIDO. They may belong to the issuing\naccount or to accounts it manages with VIEW access. Their\nsum is the disbursement amount (never typed by hand). Audit traceability.\nOnly present on lines with line_type=SUPLIDO.\n"}}},"InvoiceTotals":{"type":"object","required":["taxable_base","total_vat","total_equivalence_surcharge","total_irpf","invoice_total"],"properties":{"taxable_base":{"type":"number","example":2000,"description":"Total taxable base (can be negative in corrective invoices)"},"total_discounts":{"type":"number","default":0,"example":0,"description":"Total discounts applied (can be negative in corrective invoices)"},"vat_breakdown":{"type":"array","items":{"type":"object","required":["type","base","amount"],"properties":{"type":{"type":"number","example":21},"base":{"type":"number","example":2000},"amount":{"type":"number","example":420},"regime_key":{"type":"string","example":"18","description":"VeriFactu regime key of the rows grouped here. Rows are grouped by\n(tax type, rate, regime key), so an invoice mixing general-regime and\nequivalence-surcharge lines at the same rate yields TWO rows at\n`type: 21` that only this field tells apart (`01` vs `18`). Index by\n`(type, regime_key)`, never by `type` alone.\n"}}}},"total_vat":{"type":"number","example":420,"description":"Total indirect tax (IVA, IGIC, IPSI and other rates), not only VAT.\nCan be negative in corrective invoices.\n"},"surcharge_breakdown":{"type":"array","items":{"type":"object","required":["type","base","amount"],"properties":{"type":{"type":"number"},"base":{"type":"number"},"amount":{"type":"number"}}}},"total_equivalence_surcharge":{"type":"number","default":0,"example":0,"description":"Total equivalence surcharge (can be negative in corrective invoices)"},"irpf_breakdown":{"type":"array","items":{"type":"object","required":["type","base","amount"],"properties":{"type":{"type":"number"},"base":{"type":"number"},"amount":{"type":"number"}}}},"total_irpf":{"type":"number","default":0,"example":300,"description":"Total personal income tax withheld (can be negative in corrective invoices)"},"invoice_total":{"type":"number","example":2120,"description":"Fiscal total of the invoice: taxable base + VAT + equivalence surcharge − IRPF\n(can be negative in corrective invoices). It does **not** include disbursements\n(SUPLIDO lines), so on an invoice made only of disbursements it is 0. What the client\nactually pays is `total_to_pay`.\n"},"total_disbursements":{"type":"number","default":0,"example":0,"description":"Sum of SUPLIDO lines (payments on behalf of the client, art. 78.Tres.3 LIVA).\nExcluded from the taxable base, VAT and the amounts declared to VeriFactu.\n"},"total_to_pay":{"type":"number","example":2120,"description":"Total amount paid by the client = `invoice_total` + `total_disbursements`.\nThis is the amount on the PDF and the actual charge. When there are no\ndisbursements (suplidos) it matches `invoice_total`.\n"}}},"PaymentInfo":{"type":"object","properties":{"method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"description":"Preferred payment method. Omitted, `BANK_TRANSFER` applies.\nIf NONE is selected, no payment information will be shown on the invoice.\n"},"iban":{"$ref":"#/components/schemas/IBAN"},"swift":{"$ref":"#/components/schemas/SWIFT"},"payment_term_days":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Payment term in days. When marking an invoice as paid, every field of this object\nthat travels replaces the stored one and every omitted field keeps its current value.\n","example":30}}},"VeriFactu":{"type":"object","description":"**Record of what was applied to this invoice** — not a per-invoice preference.\n\nWhether an invoice is registered with the AEAT is a fact of the *taxpayer*: if the issuing\ntax ID is under the VeriFactu regime in that environment, every one of its invoices is\nregistered; if it is not, none is. That is resolved once, at issue time, against the state\nof the account at that instant, and what this block reports is the outcome — the receipt of\nan irreversible decision. It cannot be requested, overridden or changed per invoice.\n\nPresent on every invoice, whatever its status. **Absent on a proforma**: a proforma is not a\nfiscal document and is never registered, so there is no outcome to report — read\n`verifactu` as \"not applicable\" when the key is missing or carries no value.\n","properties":{"enabled":{"type":"boolean","description":"Whether this invoice was registered with the AEAT under VeriFactu. Read-only: it records\nthe regime of the issuing tax ID at the moment of issuance.\n"},"invoice_hash":{"type":"string","description":"SHA-256 hash of the registration record, as VeriFactu defines it. Present from the\nmoment the registration is submitted, while `submission_status` is still `PENDING`,\nand kept whatever the AEAT answers.\n","example":"3A5B7C9D1E2F3A4B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5E6F7A8B"},"registration_number":{"type":"string","description":"Identifier (UUID) of this record in the VeriFactu submission, assigned when it is\nsubmitted. It is not an AEAT code: quote it when you ask BeeL about the record.\n","example":"4f8c2a1e-9b3d-4e7a-8c5f-1d2e3f4a5b6c"},"qr_url":{"type":"string","description":"AEAT verification URL encoded in the invoice QR code. Present from the moment the\nregistration is submitted, while `submission_status` is still `PENDING`.\n","example":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2025%2F0042&fecha=20-01-2025&importe=1590.00"},"qr_base64":{"type":"string","nullable":true,"description":"QR code as base64-encoded PNG for embedding in custom PDFs. Present from the moment\nthe registration is submitted, while `submission_status` is still `PENDING`; it does\nnot wait for the AEAT to accept the record.\n","example":"iVBORw0KGgoAAAANSUhEUgAAAMgAAADI..."},"registered_at":{"type":"string","format":"date-time","description":"VeriFactu registration date and time"},"submission_status":{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"},"skip_reason":{"allOf":[{"$ref":"#/components/schemas/VeriFactuSkipReason"}],"nullable":true,"description":"Why this invoice was not submitted to AEAT, when a submission was expected and\nomitted. Null in every other case, including invoices that are not subject to\nVeriFactu at all.\n"},"error_code":{"$ref":"#/components/schemas/VeriFactuErrorCode"},"error_message":{"type":"string","nullable":true,"description":"Human-readable reason for the outcome. When the AEAT reported a remark or an error on the\nrecord, it is the AEAT's own description. When BeeL. decided the outcome (the submission\nwas rejected before reaching the AEAT, or BeeL. stopped waiting for a final answer), it\nis a message written by BeeL., in the language of the request.\n","example":"Factura ya existe en el sistema"}}},"VeriFactuSubmissionStatus":{"type":"string","description":"Submission status of an invoice's VeriFactu record to AEAT.\n\nSingle vocabulary for the whole axis: the same values are published in\n`verifactu.submission_status` of an invoice and accepted by the `verifactu_status`\nfilter of `GET /v1/invoices`, so a value read from an invoice can be fed straight\nback into the filter. `ACCEPTED` does not include the records accepted with errors, which\nhave their own value. New values may be added over time: treat any value you do not\nrecognise as an unknown status.\n\n* `PENDING` — queued, AEAT has not answered yet. A temporary AEAT server error also\n  stays `PENDING`: BeeL. retries it automatically, and it only becomes `REJECTED` if the\n  retries run out. So does a submission that failed because of a technical problem on\n  BeeL.'s side: BeeL. submits it again once the problem is fixed. Do not void it or issue\n  another invoice for it.\n* `ACCEPTED` — accepted by AEAT without remarks.\n* `ACCEPTED_WITH_ERRORS` — accepted by AEAT, which also reported errors in the record\n  (see `error_code` / `error_message`). The record stands, but the data AEAT flagged\n  must be reviewed. Do not treat it as a clean `ACCEPTED`.\n* `VOIDED` — a cancellation record was accepted by AEAT.\n* `REJECTED` — rejected by AEAT, or the submission was rejected by the provider\n  before reaching AEAT (see `error_code` / `error_message`).\n* `NOT_SUBMITTED` — the invoice is issued with VeriFactu enabled but has no live\n  record: the submission fell through (lost event, exhausted retries) and AEAT\n  does not know the invoice exists. Transient right after issuing (the async\n  submission may still be in flight); if it persists, the registration needs to\n  be re-driven.\n\nDrafts and scheduled invoices have no submission to describe yet and omit the\nfield. Invoices with `verifactu.enabled = false` are outside this axis and are\nselected with the `verifactu_enabled` filter.\n","enum":["PENDING","ACCEPTED","ACCEPTED_WITH_ERRORS","VOIDED","REJECTED","NOT_SUBMITTED"],"example":"ACCEPTED"},"Recipient":{"type":"object","description":"Invoice recipient: either a registered customer (`customer_id`) or the recipient's\ndata inline (`legal_name`, `nif`, `address`…), never both. Sending `customer_id`\ntogether with any other recipient field returns 422\n`RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE`, on create and on edit.\n\nAll fields are optional at schema level, but for an ad-hoc recipient (no\ncustomer_id) on non-SIMPLIFIED invoices the API requires legal_name, address and\nnif (or alternative_id); omitting the address returns 422\nRECIPIENT_ADDRESS_REQUIRED.\n","properties":{"customer_id":{"type":"string","format":"uuid","description":"UUID of a registered customer. The invoice takes the recipient data stored on\nthat customer. Send it alone: combined with any other recipient field it returns\n422 `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE`. To change the recipient's data, edit\nthe customer or send the data inline without `customer_id`.\n","example":"4f244735-980b-8d9c-80e8-6331fa0b1958"},"legal_name":{"type":"string","minLength":1,"maxLength":255,"description":"Recipient legal name. Required when customer_id is not provided\n(except for SIMPLIFIED invoices where all fields are optional).\n","example":"Tech Solutions SL"},"trade_name":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Recipient trade name (optional)","example":"TechSol"},"nif":{"type":"string","pattern":"^[A-Za-z0-9]{9}$","minLength":9,"maxLength":9,"description":"Spanish Tax ID (9 alphanumeric characters).\nRequired when customer_id is not provided and alternative_id is absent.\nNot accepted on SIMPLIFIED invoices: BeeL. requires a STANDARD invoice when the\nrecipient is identified.\n","example":"B12345674"},"alternative_id":{"allOf":[{"$ref":"#/components/schemas/AlternativeIdentifier"},{"description":"Alternative identifier for foreign customers (mutually exclusive with nif).\nNot accepted on SIMPLIFIED invoices, like `nif`: BeeL. requires a STANDARD\ninvoice when the recipient is identified.\n"}]},"address":{"$ref":"#/components/schemas/Address"},"phone":{"$ref":"#/components/schemas/PhoneInput"},"email":{"$ref":"#/components/schemas/Email"}},"example":{"customer_id":"4f244735-980b-8d9c-80e8-6331fa0b1958"}},"CreateInvoiceRequest":{"type":"object","required":["type","recipient","lines"],"properties":{"type":{"allOf":[{"$ref":"#/components/schemas/InvoiceType"}],"description":"Invoice type to create. `CORRECTIVE` is **not** accepted here: a corrective\ninvoice is always created from the invoice it corrects, via\n`POST /v1/companies/{company_id}/invoices/{invoice_id}/corrective`, which is\nwhere its rectification type and VeriFactu code (R1–R5) are declared.\n"},"series_id":{"type":"string","format":"uuid","description":"Invoicing series. It must be a series of the invoice's type: a series numbers only\ndocuments of its own type (RD 1619/2012, arts. 6.1.a and 7.1.a), otherwise\n`422 SERIES_INCOMPATIBLE_DOC_TYPE`. If omitted, the company's default series of that\ntype is used; the `SIMPLIFIED` one is created on first use if the company has none,\nwhile a missing `STANDARD` default fails with `422 SERIES_DEFAULT_NOT_FOUND`.\n","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"operation_date":{"type":"string","format":"date","description":"Date when the operation actually occurred. Optional.\n\nUse when invoicing for a past operation (e.g., services delivered last month\nbut invoiced this month). **Must be today or a past date**, and not more than twenty\nyears before today (AEAT does not accept an older one): an older date answers\n`422 OPERATION_DATE_TOO_OLD`, on creation and again on issue.\n\nIf omitted, the operation date is assumed to be the same as the issue date (today).\n\nThe `issue_date` is not an input: BeeL sets it to the day the invoice is issued.\nTo issue an invoice on a future date, create a draft and use\n`POST /v1/invoices/{invoice_id}/schedule`.\n","example":"2025-01-10"},"due_date":{"type":"string","format":"date","description":"Payment due date. If not specified, calculated according to payment method.\n**Must be the same as or after the issue date (today).**\n","example":"2025-02-14"},"valid_until":{"type":"string","format":"date","description":"Offer validity date. Only rendered on PROFORMA invoices; on any other\ninvoice type the field is inert (accepted and stored, but never shown on\nthe document). Optional and purely informational — nothing is triggered\nautomatically when it passes. Not to be confused with `due_date` (payment\ndue date).\n","example":"2025-02-28"},"recipient":{"$ref":"#/components/schemas/Recipient"},"lines":{"type":"array","items":{"type":"object","required":["quantity"],"properties":{"description":{"type":"string","maxLength":2000,"description":"Description of invoiced concept. Required for NORMAL lines;\noptional for SUPLIDO lines (may be empty or absent).\n","example":"Web application development - Sprint 1"},"quantity":{"type":"number","description":"Product/service quantity (can be negative for franchises or discounts)","example":40},"unit":{"type":"string","example":"hours"},"unit_price":{"type":"number","minimum":0,"maximum":999999.9999,"description":"Unit price before taxes. `0` is accepted (a discount granted before or\nsimultaneously with the sale, e.g. a free introductory month).\nSupports up to 4 decimal places for micro-pricing (e.g., €0.0897/unit for labels, packaging).\n","example":50},"total_excluding_tax":{"type":"number","maximum":99999999.99,"example":1,"description":"Declared line total excluding taxes (total-declared mode, e.g. 300 units\ninvoiced for exactly 1.00). The taxable base of the line is EXACTLY this\namount — it is never recalculated from the unit price. The unit price\nbecomes derived and informational (`total / quantity`, 4 decimals).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`): any\ndiscount is already included in the declared total. Can be negative\nin corrective invoices.\n"},"total_including_tax":{"type":"number","maximum":99999999.99,"example":100,"description":"Declared line total including taxes (tax-inclusive total-declared\nmode): what the customer paid for this line — taxable base + VAT +\nequivalence surcharge. IRPF withholding is NOT part of it (it is a\nretention, not price; it is computed on the derived base as usual).\nThe engine works the breakdown backwards from the unrounded base\n(`base_raw = total / (1 + vat + surcharge)`, DGT V1919-18) so the\nrounded amounts add up to the declared total exactly (e.g. 100.00\nat 21% → 82.64 + 17.36 = 100.00). On exempt or 0% lines it is\nequivalent to `total_excluding_tax` (base = total, quota 0).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`).\nCan be negative in corrective invoices.\n"},"discount_percentage":{"type":"number","minimum":0,"maximum":100,"default":0,"example":10,"description":"Discount percentage applied (0-100)"},"main_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"}],"description":"Main tax of the line: regime (IVA/IGIC/IPSI/OTHER), percentage and regime key.\n\n**Mandatory on `NORMAL` lines.** It is never defaulted: omitting it is rejected\nwith `422 LINE_MAIN_TAX_REQUIRED`, and is never filled in from the company's\n`default_main_tax` — that setting is a UI prefill, not an API default, because\nwhich tax a line bears is a fiscal decision that drives the VeriFactu breakdown\nand the tax printed on the invoice.\n\n**Forbidden on `SUPLIDO` lines**, which are payments made on behalf of the\nclient and sit outside VAT (art. 78.Tres.3 LIVA): sending one is rejected with\n`422 LINE_SUPLIDO_MUST_HAVE_NO_TAX`. That conditional obligation is why the\nfield is not listed under `required`: OpenAPI 3.0 cannot express \"required\nunless `line_type` is `SUPLIDO`\".\n\nA 0 % under IVA or IPSI is not a rate but the exemption sentinel and needs an\n`exemption_reason`; see `TaxInfo`.\n"},"equivalence_surcharge_rate":{"allOf":[{"$ref":"#/components/schemas/EquivalenceSurchargePercentage"}],"description":"Equivalence surcharge rate for this line.\n\n**Default behaviour:** if omitted and the company has\n`apply_equivalence_surcharge: true` in its tax configuration,\nthe line inherits the surcharge — and its percentage is a legal\nfunction of the line's VAT rate, not the configured default:\n21 ↔ 5.2, 10 ↔ 1.4, 5 ↔ 0.62, 4 ↔ 0.5 (the pairs enumerated by\n`EquivalenceSurchargePercentage`). A company configured with\n`default_equivalence_surcharge: 5.2` therefore produces 1.4 on a\n10% line, not 5.2.\n\n**The inheritance also rewrites the line's `regime_key` from `01`\nto `18`** (special regime for equivalence surcharge). This is\ndeliberate: a surcharge and general regime `01` are fiscally\nincoherent, so the line comes back as `18` even if `01` was sent.\n\nTo issue a line **without** surcharge under such a company, send\n`equivalence_surcharge_rate: 0` explicitly — exactly as with\n`irpf_rate`: the `01` regime key is then respected and no\nsurcharge is applied. Sending an explicit rate greater than 0\ntogether with `regime_key: \"01\"` is **not** rejected: the very\nsame rewrite applies and the line comes back as `18`.\n\n**Any other regime with a surcharge is rejected** with\n`422 SURCHARGE_REQUIRES_REGIME`. Only the general regime `01`\n**rewrites**; REBU (`03`), exports (`02`), OSS (`17`)… never do,\nbecause a surcharge under them is fiscally invalid — an error to\nsurface, not a shorthand to normalise.\n"},"irpf_rate":{"allOf":[{"$ref":"#/components/schemas/IrpfPercentage"}],"description":"IRPF withholding rate for this line.\n\n**Default behaviour:** if omitted, the line inherits the\naccount's default IRPF rate (configured in the tax profile,\ne.g. 15%). To issue a line **without** withholding you must\nsend `irpf_rate: 0` explicitly. On SIMPLIFIED invoices (F2)\nIRPF withholding is **not allowed** (AEAT forbids it on F2):\nsending an `irpf_rate` other than 0 is **rejected** with\n`SIMPLIFICADA_FORBIDS_IRPF` — it is not coerced to 0. Omit the\nfield or send `irpf_rate: 0` on F2 lines. On all other invoice\ntypes an explicit value is always respected.\n\nThe rate must be one the issuer can bear (see `WithholdingOptions` in the\ntax configuration). No entity pays IRPF: a legal person or a permanent\nestablishment (NIF starting with `A`, `B`, `C`, `D`, `F`, `G`, `Q`, `R`,\n`U` or `W`) only accepts `0`, `19`, `24` and `9.5` (rents in Ceuta and\nMelilla), a non-resident entity (`N`) `0`, `19` and `24`, and the State,\nan Autonomous Community or a local entity (`S`, `P`) only `0`; any other\nrate is rejected with `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`. `9.5` from an\nindividual is rejected with `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`: under IRPF the Ceuta\nand Melilla reduced rates are `6`, `2.8` and `7.6`. Checked on creation,\non edit and again on issue; corrective invoices are not checked: they\ncorrect by differences what the original carried.\n"},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text. Only used when exemption_reason is OTRO."},"line_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceLineType"}],"default":"NORMAL","description":"Fiscal line type. Defaults to `NORMAL`.\nUse `SUPLIDO` for payments on behalf of the final client\n(art. 78.Tres.3 LIVA). Requires `source_invoice_reference`.\n"},"source_invoice_reference":{"type":"string","nullable":true,"maxLength":50,"description":"Reference to the original invoice issued by the third party in the\nclient's name. Required when `line_type=SUPLIDO`.\n"},"source_invoice_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Ids of the issued invoices that make up the SUPLIDO. They may belong to the\nissuing account or to accounts it manages with VIEW access.\nTheir sum is the amount (never typed). Audit traceability.\n"}}},"minItems":1,"example":[{"description":"Web application development - Sprint 1","quantity":40,"unit":"hours","unit_price":50,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}]},"payment_info":{"$ref":"#/components/schemas/PaymentInfo"},"notes":{"type":"string","maxLength":1000,"example":"Payment by bank transfer. Includes technical support for 30 days."},"external_ref":{"allOf":[{"$ref":"#/components/schemas/ExternalRef"}],"x-field-extra-annotation":"@com.fasterxml.jackson.annotation.JsonAlias(\"external_reference\")","description":"This field was previously named `external_reference`. The old name is still accepted as\nan alias for backwards compatibility and will be withdrawn in a future major version —\nsend `external_ref`.\n"},"metadata":{"$ref":"#/components/schemas/InvoiceMetadata"},"options":{"$ref":"#/components/schemas/InvoiceProcessingOptions"}},"example":{"type":"STANDARD","series_id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","due_date":"2025-02-14","recipient":{"customer_id":"4f244735-980b-8d9c-80e8-6331fa0b1958"},"lines":[{"description":"Web application development - Sprint 1","quantity":40,"unit":"hours","unit_price":50,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}],"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"notes":"Payment by bank transfer. Includes technical support for 30 days.","metadata":{"project_code":"PROJ-123","client_reference":"REF-2025-001"},"options":{"issue_directly":true,"wait_for_pdf":false,"send_automatically":false}}},"InvoiceProcessingOptions":{"type":"object","description":"Controls how the invoice is processed after creation.\nAll fields default to `false` if not specified.\n\nVeriFactu is **not** an option here: whether an invoice is registered with AEAT is a\nfact of the tax identity (NIF x environment), resolved at issue time against the\ncompany's regime. See `verifactu.enabled` in the invoice response for what was applied.\n\n**Common combinations:**\n- Draft (default): omit `options` or set all to `false`\n- Issue immediately: `{ issue_directly: true }`\n- Issue + wait for PDF: `{ issue_directly: true, wait_for_pdf: true }`\n- Issue + send email: `{ issue_directly: true, send_automatically: true }`\n- Full automation: `{ issue_directly: true, wait_for_pdf: true, send_automatically: true, email_config: { ... } }`\n","properties":{"issue_directly":{"type":"boolean","default":false,"description":"If `true`, creates the invoice directly as **ISSUED** with a definitive number and PDF.\nIf `false` (default), creates as **DRAFT** without number (editable, no PDF).\n"},"wait_for_pdf":{"type":"boolean","default":false,"description":"Only applies when `issue_directly` is `true`.\nIf `true`, waits for PDF generation before returning the response (~1-3s).\nIf `false` (default), PDF is generated asynchronously in the background.\n"},"send_automatically":{"type":"boolean","default":false,"description":"Only applies when `issue_directly` is `true`.\nIf `true`, sends the invoice by email with PDF attachment after issuing.\nThe email is sent asynchronously after the invoice is issued.\n"},"attach_source_invoices":{"type":"boolean","default":false,"description":"Only applies when `send_automatically` is `true`. If `true`, the email sent after\nissuing also attaches a ZIP (`suplidos_<invoice-number>.zip`) with the PDFs of the\nsource invoices referenced by the invoice's SUPLIDO consolidation lines\n(`source_invoice_ids`). Each PDF inside the ZIP is named\n`<invoice-number>_<issuer-tax-id>.pdf`. Access to sources owned by managed accounts is\nre-checked with the same rules as issuing, and the request fails synchronously with an\nactionable error — never a partial ZIP — if the invoice has no consolidation sources\n(`ATTACH_SOURCE_INVOICES_NO_SOURCES`), a source is not reachable\n(`ATTACH_SOURCE_INVOICE_UNAVAILABLE`) or a source has no generated PDF\n(`ATTACH_SOURCE_PDF_MISSING`). The flag belongs to this issuing act only: it is never\nstored on the invoice.\n"},"email_config":{"allOf":[{"$ref":"#/components/schemas/EmailConfiguration"}],"description":"Only applies when `send_automatically` is `true`.\nOverrides default email settings. If it names no recipients, the email goes to the\ncustomer's `billing_emails`, or to the customer's `email` when there are none.\n"}},"example":{"issue_directly":true,"wait_for_pdf":false,"send_automatically":false}},"UpdateInvoiceRequest":{"type":"object","description":"Partial update for a DRAFT invoice. Only fields present in the body are applied;\nomitted fields preserve their existing value.\n","properties":{"type":{"allOf":[{"$ref":"#/components/schemas/InvoiceType"}],"description":"New invoice type. Allowed transitions: `STANDARD` ↔ `SIMPLIFIED`.\nTransitions to/from `CORRECTIVE` are rejected (corrective invoices have a\ndedicated `POST /v1/invoices/{invoice_id}/corrective` endpoint).\nTransitions to/from `PROFORMA` are rejected too — a proforma is converted\ninto an invoice via its dedicated conversion flow, never by editing its type.\n"},"series_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Series ID. If provided, changes the invoice series (only for DRAFT invoices).\nThe invoice number will be reassigned from the new series when issued.\n"},"operation_date":{"type":"string","format":"date","nullable":true,"description":"Date when the operation occurred. **Must be today or a past date**, and not more than\ntwenty years before today (AEAT does not accept an older one): an older date answers\n`422 OPERATION_DATE_TOO_OLD`, on update and again on issue.\nSet to null to clear (operation date = issue date).\nIf not provided, keeps the existing value.\n"},"due_date":{"type":"string","format":"date","nullable":true,"description":"New due date. Must be the same as or after `issue_date`.\nSet to null to clear the due date. If not provided, keeps the existing value.\n"},"valid_until":{"type":"string","format":"date","nullable":true,"description":"Offer validity date. Only rendered on PROFORMA invoices; on any other\ninvoice type the field is inert. Purely informational.\nSet to null to clear it. If not provided, keeps the existing value.\n"},"recipient":{"allOf":[{"$ref":"#/components/schemas/Recipient"}],"description":"Replaces the recipient when present. Provide `customer_id` to switch to a\nregistered client, or inline `legal_name`/`nif`/`address` for an ad-hoc receptor,\nnot both (422 `RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE`). Omit to keep the current\nrecipient.\n"},"lines":{"type":"array","items":{"type":"object","required":["quantity"],"properties":{"description":{"type":"string","maxLength":2000,"description":"Required for NORMAL lines; optional for SUPLIDO lines.\n"},"quantity":{"type":"number"},"unit":{"type":"string"},"unit_price":{"type":"number","minimum":0,"maximum":999999.9999,"description":"Unit price before taxes. `0` is accepted (a discount granted before or\nsimultaneously with the sale, e.g. a free introductory month).\nSupports up to 4 decimal places for micro-pricing (e.g., €0.0897/unit for labels, packaging).\nFinal amounts are always rounded to 2 decimals.\n"},"total_excluding_tax":{"type":"number","maximum":99999999.99,"example":1,"description":"Declared line total excluding taxes (total-declared mode, e.g. 300 units\ninvoiced for exactly 1.00). The taxable base of the line is EXACTLY this\namount — it is never recalculated from the unit price. The unit price\nbecomes derived and informational (`total / quantity`, 4 decimals).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`): any\ndiscount is already included in the declared total. Can be negative\nin corrective invoices.\n"},"total_including_tax":{"type":"number","maximum":99999999.99,"example":100,"description":"Declared line total including taxes (tax-inclusive total-declared\nmode): what the customer paid for this line — taxable base + VAT +\nequivalence surcharge. IRPF withholding is NOT part of it (it is a\nretention, not price; it is computed on the derived base as usual).\nThe engine works the breakdown backwards from the unrounded base\n(`base_raw = total / (1 + vat + surcharge)`, DGT V1919-18) so the\nrounded amounts add up to the declared total exactly (e.g. 100.00\nat 21% → 82.64 + 17.36 = 100.00). On exempt or 0% lines it is\nequivalent to `total_excluding_tax` (base = total, quota 0).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`).\nCan be negative in corrective invoices.\n"},"discount_percentage":{"type":"number"},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"$ref":"#/components/schemas/EquivalenceSurchargePercentage"},"irpf_rate":{"allOf":[{"$ref":"#/components/schemas/IrpfPercentage"}],"description":"IRPF withholding rate for this line.\n\n**Default behaviour:** if omitted, the line inherits the\naccount's default IRPF rate (configured in the tax profile,\ne.g. 15%). To issue a line **without** withholding you must\nsend `irpf_rate: 0` explicitly. On SIMPLIFIED invoices (F2)\nIRPF withholding is **not allowed** (AEAT forbids it on F2):\nsending an `irpf_rate` other than 0 is **rejected** with\n`SIMPLIFICADA_FORBIDS_IRPF` — it is not coerced to 0. Omit the\nfield or send `irpf_rate: 0` on F2 lines. On all other invoice\ntypes an explicit value is always respected.\n\nThe rate must be one the issuer can bear (see `WithholdingOptions` in the\ntax configuration). No entity pays IRPF: a legal person or a permanent\nestablishment (NIF starting with `A`, `B`, `C`, `D`, `F`, `G`, `Q`, `R`,\n`U` or `W`) only accepts `0`, `19`, `24` and `9.5` (rents in Ceuta and\nMelilla), a non-resident entity (`N`) `0`, `19` and `24`, and the State,\nan Autonomous Community or a local entity (`S`, `P`) only `0`; any other\nrate is rejected with `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`. `9.5` from an\nindividual is rejected with `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`: under IRPF the Ceuta\nand Melilla reduced rates are `6`, `2.8` and `7.6`. Checked on creation,\non edit and again on issue; corrective invoices are not checked: they\ncorrect by differences what the original carried.\n"},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500},"line_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceLineType"}],"description":"Fiscal line type. Omitted, `NORMAL` applies.\nUse `SUPLIDO` for payments on behalf of the final client\n(art. 78.Tres.3 LIVA). Requires `source_invoice_reference`.\n"},"source_invoice_reference":{"type":"string","nullable":true,"maxLength":50,"description":"Reference to the original invoice issued by the third party in the\nclient's name. Required when `line_type=SUPLIDO`.\n"},"source_invoice_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Ids of the issued invoices that make up the SUPLIDO. They may belong to the\nissuing account or to accounts it manages with VIEW access.\nTheir sum is the amount (never typed). Audit traceability.\n"}}}},"payment_info":{"allOf":[{"$ref":"#/components/schemas/PaymentInfo"}],"description":"Replaces the payment information when present (sets method/IBAN/SWIFT/term days).\nOmit to keep the current payment info.\n"},"notes":{"type":"string"},"options":{"type":"object","description":"Processing options for the draft. Fields are optional and follow partial update\nsemantics: omitted fields preserve the existing value.\n","properties":{"send_automatically":{"type":"boolean","description":"Whether the invoice should be auto-emailed after issuing."},"email_config":{"allOf":[{"$ref":"#/components/schemas/EmailConfiguration"}],"nullable":true,"description":"Email configuration for auto-send. null clears the existing config."}}}}},"EmailConfiguration":{"type":"object","description":"Who the invoice is emailed to when `send_automatically` is true. Every address in\n`recipients` and `cc` must have a domain with a dot, or the request answers\n`422 INVALID_EMAIL_FORMAT`. With `send_automatically` true and no recipients here, no\n`customer_id` and no recipient email, there is nobody to send to: `422`\n`AUTOMATIC_EMAIL_CONFIG_INVALID`.\n","required":["recipients"],"properties":{"recipients":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"minItems":1,"description":"List of recipient emails (at least 1 required)","example":["client@example.com"]},"cc":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"List of CC emails (optional)","example":["copy@example.com"]},"subject":{"type":"string","minLength":1,"maxLength":200,"description":"Custom email subject (optional, if not specified uses a default)","example":"Invoice 2025/0001 - Web development services"},"message":{"type":"string","minLength":1,"maxLength":2000,"description":"Custom message (optional, added to email body)","example":"Dear customer, please find attached the invoice for the services provided. Thank you for your trust."}},"example":{"recipients":["client@example.com"],"cc":["accounting@example.com"],"subject":"Invoice 2025/0001 - Development services","message":"Please find attached the requested invoice. We remain at your disposal for any clarification."}},"SendEmailRequest":{"type":"object","properties":{"recipients":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"Recipients of the email. When omitted, the invoice's `email_config` recipients apply,\nthen the customer's `billing_emails`, then the customer's `email`.\n"},"cc":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"CC recipients. Copied addresses count as recipients of the message: they are subject\nto the same sending restrictions and to the same quota as the addresses in `recipients`.\nWhen omitted, the CC addresses configured in the sender's email defaults apply; send an\nempty array to deliver the message without any copy.\n"},"subject":{"type":"string","minLength":1,"maxLength":200,"description":"Email subject (optional, if not specified uses a default)"},"message":{"type":"string","minLength":1,"maxLength":2000,"description":"Custom message (optional, added before standard message)"},"attach_pdf":{"type":"boolean","default":true},"attach_source_invoices":{"type":"boolean","default":false,"description":"Attach a ZIP archive (`suplidos_<invoice-number>.zip`) containing the PDFs of the source invoices referenced by the invoice's SUPLIDO consolidation lines (`source_invoice_ids`). Each PDF inside the ZIP is named `<invoice-number>_<issuer-tax-id>.pdf`. Requires `attach_pdf: true` (the ZIP accompanies the invoice PDF). Access to sources owned by managed accounts is re-checked at send time with the same rules as issuing; the request fails with an actionable error — never a partial ZIP — if the invoice has no consolidation sources (`ATTACH_SOURCE_INVOICES_NO_SOURCES`), a source is not reachable (`ATTACH_SOURCE_INVOICE_UNAVAILABLE`), a source has no generated PDF (`ATTACH_SOURCE_PDF_MISSING`), or the ZIP exceeds the size limit (`ATTACH_SOURCE_ZIP_TOO_LARGE`)."},"language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"description":"Language of the email. When omitted, the issuing company's `email_language` applies, and Spanish (`es`) when the company has none set."}}},"CreateCorrectiveInvoiceRequest":{"type":"object","required":["rectification_type","rectification_code","reason"],"properties":{"rectification_type":{"$ref":"#/components/schemas/RectificationType"},"rectification_code":{"$ref":"#/components/schemas/VeriFactuRectificationCode"},"reason":{"type":"string","minLength":10,"maxLength":1000,"description":"Detailed reason for rectification (minimum 10 characters)","example":"Amount correction due to calculation error in hours worked during the project"},"lines":{"type":"array","items":{"type":"object","required":["quantity"],"properties":{"description":{"type":"string","maxLength":2000,"description":"Concept description. Required.","example":"Adjustment for incorrectly invoiced hours"},"quantity":{"type":"number","description":"Quantity (can be negative for corrective invoices)","example":-10},"unit":{"type":"string","example":"hours"},"unit_price":{"type":"number","maximum":999999.9999,"description":"Unit price before taxes (can be negative in corrective invoices).\nSupports up to 4 decimal places for micro-pricing (e.g., €0.0897/unit for labels, packaging).\nFinal amounts are always rounded to 2 decimals.\n","example":50},"total_excluding_tax":{"type":"number","maximum":99999999.99,"example":1,"description":"Declared line total excluding taxes (total-declared mode, e.g. 300 units\ninvoiced for exactly 1.00). The taxable base of the line is EXACTLY this\namount — it is never recalculated from the unit price. The unit price\nbecomes derived and informational (`total / quantity`, 4 decimals).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`): any\ndiscount is already included in the declared total. Can be negative\nin corrective invoices.\n"},"total_including_tax":{"type":"number","maximum":99999999.99,"example":100,"description":"Declared line total including taxes (tax-inclusive total-declared\nmode): what the customer paid for this line — taxable base + VAT +\nequivalence surcharge. IRPF withholding is NOT part of it (it is a\nretention, not price; it is computed on the derived base as usual).\nThe engine works the breakdown backwards from the unrounded base\n(`base_raw = total / (1 + vat + surcharge)`, DGT V1919-18) so the\nrounded amounts add up to the declared total exactly (e.g. 100.00\nat 21% → 82.64 + 17.36 = 100.00). On exempt or 0% lines it is\nequivalent to `total_excluding_tax` (base = total, quota 0).\nEach line must carry exactly one of `unit_price`, `total_excluding_tax`\nor `total_including_tax` (anything else is rejected with\n`LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`). Incompatible with\n`discount_percentage` (`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT`).\nCan be negative in corrective invoices.\n"},"discount_percentage":{"type":"number","minimum":0,"maximum":100,"default":0},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"allOf":[{"$ref":"#/components/schemas/EquivalenceSurchargePercentage"}],"description":"Equivalence surcharge rate for this line of the corrective\ninvoice.\n\n**Default behaviour:** if omitted, the line inherits the\nsurcharge **regime** of the invoice being amended — not the\ncompany's current tax profile, whose default does not apply to\ncorrective invoices. What travels from the original is the\non/off signal, not the rate: the rate is re-derived from this\nline's own VAT (21→5.2, 10→1.4, 5→0.62, 4→0.5), so a\ncorrective line at 10% gets 1.4 even when the original line it\namends was at 21%. If the original was outside the regime the\nline is pinned to `0`, so today's profile never adds a\nsurcharge to the credit note of an invoice that carried none.\nAn explicit value is always respected. If the original applies\nthe surcharge on some lines but not others there is no regime\nto inherit and the request fails with\n`422 CORRECTIVE_ORIGINAL_MIXED_SURCHARGE`: send\n`equivalence_surcharge_rate` on every line. The original's\n`SUPLIDO` lines carry no surcharge and are left out of that check.\n"},"irpf_rate":{"allOf":[{"$ref":"#/components/schemas/IrpfPercentage"}],"description":"IRPF withholding rate for this line of the corrective invoice.\n\n**Default behaviour:** if omitted, the line inherits the rate\nof the invoice being amended — **not** the account's default\nIRPF rate from the tax profile, whose default does not apply to\ncorrective invoices: a profile that changed after the original\nwas issued must not alter what the credit note withholds. An\nexplicit value is always respected, `0` included, which is how\nyou issue a line **without** withholding. If the original\nwithholds different rates on different lines there is nothing\nunambiguous to inherit and the request fails with\n`422 CORRECTIVE_ORIGINAL_MIXED_IRPF`: send `irpf_rate` on every\nline. The original's `SUPLIDO` lines carry no IRPF and are left\nout of that check. On SIMPLIFIED invoices (F2) IRPF withholding is **not\nallowed** (AEAT forbids it on F2): sending an `irpf_rate` other\nthan 0 is **rejected** with `SIMPLIFICADA_FORBIDS_IRPF` — it is\nnot coerced to 0. Omit the field or send `irpf_rate: 0` on F2\nlines.\n"},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500},"line_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceLineType"}],"description":"Only `NORMAL` is accepted, which is also what an omitted value means. A\n`PARTIAL` corrective adjusts the taxable base or the tax of the original, and a\ndisbursement (`SUPLIDO`) is neither: it is outside the taxable base (Ley 37/1992,\nart. 78.Tres.3.º), so there is nothing in it to rectify. A `SUPLIDO` line fails\nwith `422 CORRECTIVE_PARTIAL_SUPLIDO_NOT_ALLOWED`. If an amount invoiced as a\ndisbursement did not qualify as one, it was part of the taxable base: rectify it\nas a `NORMAL` line with its tax.\n"}}},"description":"**TOTAL**: not accepted. A `TOTAL` corrective rectifies what is still invoiced on the\noriginal —its lines and those of its live correctives, negated, `SUPLIDO` lines included\nas they were— and a request with `lines` fails with `422 RECTIFICATIVA_TOTAL_CON_LINEAS`.\n**PARTIAL**: required. The adjustment lines, with positive or negative amounts; they\nmay not take the base of any rate below zero (`422 CORRECTIVE_EXCEEDS_INVOICED_AMOUNT`).\nThey are `NORMAL` lines only (`422 CORRECTIVE_PARTIAL_SUPLIDO_NOT_ALLOWED`).\n"},"circumstance_date":{"type":"string","format":"date","description":"When the circumstance that causes the rectification took place, if it is one of article\n80 of the VAT Act (Ley 37/1992): a discount granted after the sale, an operation cancelled\nor a price changed after it took place, the customer's insolvency, a bad debt. Optional.\n\nA corrective must be issued within four years from when the tax accrued or, for those\ncauses, from when the circumstance took place (RD 1619/2012, art. 15.3). Without this\ndate the four years count from the original's operation date (its `operation_date`, or\nits `issue_date` when it has none), the stricter of the two. Past the deadline the request\nfails with `422 CORRECTIVE_OUT_OF_TIME`.\n\nOnly for `R1`, `R2`, `R3` and `R5`: `R4` covers causes other than article 80, and sending\nit with `R4` fails with `422 CORRECTIVE_CIRCUMSTANCE_DATE_NOT_APPLICABLE`. It must lie\nbetween the original's operation date and today\n(`422 CORRECTIVE_CIRCUMSTANCE_DATE_OUT_OF_RANGE`).\n","example":"2026-03-10"},"recipient_is_business":{"type":"boolean","description":"Declares that the recipient acted as a business or professional in the operation being\nrectified. Optional, and it only matters for a bad-debt corrective (`R3`) on an\noperation whose taxable base is 50 € or less: the law allows that reduction only when\nthe recipient acted as a business or professional, and the invoice does not say so\n(Ley 37/1992, art. 80.Cuatro.A.3.ª). Without it, that `R3` fails with\n`422 CORRECTIVE_BAD_DEBT_BASE_TOO_LOW`.\n","example":true},"notes":{"type":"string","maxLength":1000,"description":"Additional observations about the rectification","example":"Rectification requested by the customer due to quantity error"},"series_id":{"type":"string","format":"uuid","description":"Series for the corrective invoice. Optional: if not specified, the company's\n**default series for corrective invoices** is used — not the original invoice's\nseries, which is an ordinary or simplified one and cannot hold a corrective.\nIf the company has no default corrective series, one is created on first use; a\nseries of the wrong type fails with `422 SERIES_INCOMPATIBLE_DOC_TYPE`.\n","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"external_ref":{"$ref":"#/components/schemas/ExternalRef"},"metadata":{"$ref":"#/components/schemas/InvoiceMetadata"},"recipient":{"allOf":[{"$ref":"#/components/schemas/Recipient"}],"writeOnly":true,"description":"Only to correct the recipient's data. A corrective invoice carries the recipient of the\ninvoice it corrects, with that invoice's data, except when the invoice recorded that same\nrecipient with a wrong name, tax ID or address: then send the corrected recipient here,\nwith `rectification_type` `PARTIAL`, `rectification_code` `R4` and no `lines`. That\ncorrective leaves the amounts unchanged and the original `RECTIFIED`.\n\n- When the original went to a registered customer, send that same `customer_id`, with\n  its data already fixed; another customer fails with\n  `422 CORRECTIVE_RECIPIENT_IS_ANOTHER_PERSON` — an invoice issued to another person is\n  corrected in full (`TOTAL`) and issued again to the right customer.\n- The same name, tax ID and address as recorded fail with\n  `422 CORRECTIVE_RECIPIENT_UNCHANGED`.\n- A `recipient` in any other corrective fails with\n  `422 CORRECTIVE_RECIPIENT_NOT_ACCEPTED`, and nothing is created.\n"},"options":{"$ref":"#/components/schemas/InvoiceProcessingOptions"}},"example":{"rectification_type":"PARTIAL","rectification_code":"R4","reason":"Amount correction due to calculation error in hours worked during the project","lines":[{"description":"Adjustment for hours error - Sprint 1","quantity":-5,"unit":"hours","unit_price":50,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}],"notes":"Rectification agreed with the customer on 2025-01-20","external_ref":"ORD-2025-0042","metadata":{"project_code":"PROJ-123"},"options":{"issue_directly":true,"send_automatically":false}}},"CreateSimplifiedExchangeRequest":{"type":"object","required":["simplified_invoice_ids","recipient"],"properties":{"simplified_invoice_ids":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string","format":"uuid"},"description":"The simplified invoices the full invoice replaces, issued and not voided, exchanged or\ncorrected. Their lines, in this order, become the lines of the full invoice. Each one\nappears once: a repeated id fails with `422 EXCHANGE_DUPLICATED_SIMPLIFIED`.\n","example":["550e8400-e29b-41d4-a716-446655440010"]},"recipient":{"allOf":[{"$ref":"#/components/schemas/Recipient"}],"description":"The customer the full invoice goes to, identified as a standard invoice requires: a\nregistered `customer_id` or inline data with a tax ID and address.\n"},"series_id":{"type":"string","format":"uuid","description":"Series of the full invoice. Optional: without it, the company's default series for\nstandard invoices is used.\n"},"notes":{"type":"string","maxLength":1000,"description":"Observations printed on the full invoice."},"options":{"$ref":"#/components/schemas/InvoiceProcessingOptions"}}},"InvoicePdfResponse":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["download_url","expires_in_seconds","file_name"],"properties":{"download_url":{"type":"string","format":"uri","description":"Pre-signed URL to download the PDF (valid for 5 minutes)","example":"https://storage.example.com/beel-invoices/invoices/user-uuid/factura-uuid.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=..."},"expires_in_seconds":{"type":"integer","description":"Seconds until the URL expires","example":300,"minimum":1},"file_name":{"type":"string","description":"Suggested filename for download","example":"factura_2025-001.pdf","pattern":"^factura_.+\\.pdf$"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"Customer":{"type":"object","required":["id","legal_name","address","created_at"],"properties":{"id":{"type":"string","format":"uuid","example":"123e4567-e89b-12d3-a456-426614174000"},"legal_name":{"type":"string","example":"Empresa Cliente SL"},"trade_name":{"type":"string","example":"EmpresaCliente"},"nif":{"$ref":"#/components/schemas/NIF"},"address":{"allOf":[{"$ref":"#/components/schemas/AddressResponse"}],"description":"Customer address as stored. Read shape: records registered before the street number\nwas collected have none, so no field is guaranteed.\n"},"phone":{"$ref":"#/components/schemas/Phone"},"email":{"type":"string","format":"email","minLength":5,"maxLength":255,"nullable":true,"description":"Main contact address (minimum valid email is 5 chars, e.g. a@b.co). Invoice emails go\nhere only when the customer has no `billing_emails`.\n"},"website":{"type":"string","maxLength":255,"nullable":true,"description":"Website URL","pattern":"^(https?://.+|)$"},"billing_emails":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"Addresses that receive the customer's invoice emails. When the send request names no\n`recipients` and the invoice has no `email_config` recipients, invoices go to these\naddresses, and not to `email`; `email` is used only when `billing_emails` is empty.\n"},"contact_person":{"type":"string","example":"María García"},"notes":{"type":"string","maxLength":1000},"preferred_payment_method":{"$ref":"#/components/schemas/PaymentInfo"},"general_discount":{"type":"number","minimum":0,"maximum":100,"default":0},"active":{"type":"boolean","default":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"alternative_id":{"$ref":"#/components/schemas/AlternativeIdentifier"}}},"AlternativeIdentifier":{"type":"object","nullable":true,"required":["type","number"],"description":"Alternative identifier for customers without Spanish Tax ID.\n\n### VeriFactu rules (checked when you send it, `422` on violation)\n- `country_code` is required, except for `PASSPORT` (03) and `NOT_REGISTERED` (07), the two\n  types AEAT accepts with `ES`: omitted, `ES` applies. Missing for any other type, the\n  identifier is rejected with `ALTERNATIVE_ID_COUNTRY_REQUIRED`, and `error.details` names\n  the field where it was sent: `alternative_id.country_code` on a customer,\n  `recipient.alternative_id.country_code` on an invoice recipient. Same code in both.\n- If `country_code = ES`, then `type` **must** be `PASSPORT` (03) or `NOT_REGISTERED` (07)\n  (`ALTERNATIVE_ID_SPAIN_INVALID_TYPE`).\n- If `type = NOT_REGISTERED` (07), then `country_code` **must** be `ES`\n  (`ALTERNATIVE_ID_REQUIRES_SPAIN`), and `number` **must** be a Spanish DNI or NIE: a\n  Spanish company is always registered (`RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE`).\n  That DNI or NIE must not be one the AEAT census identifies: if it is, the identifier is\n  rejected with `ALTERNATIVE_ID_ACTUALLY_IN_CENSUS` and has to be sent as `nif`. A customer\n  is checked when it is saved; an invoice recipient, when the invoice is issued with\n  VeriFactu. If the census does not answer, the identifier is not rejected.\n- If `type = NIF_IVA` (02), `country_code` **must** be an EU member state other than Spain\n  (`ALTERNATIVE_ID_VAT_REQUIRES_EU_COUNTRY`), and `number` **must** have that country's\n  EU VAT number structure as AEAT defines it: the country prefix (`EL` for Greece) followed\n  by the national number, e.g. `FR40303265045`, `DE123456789`, `EL094014201`\n  (`ALTERNATIVE_ID_VAT_INVALID_FORMAT`). Lowercase letters are accepted and stored in\n  uppercase. A customer from outside the EU is identified with another type, such as\n  `OTHER_DOCUMENT` or `COUNTRY_ID`.\n\nA `number` that is blank once trimmed is rejected with `ALTERNATIVE_ID_INVALID`.\n\nWell-formed is not the same as registered: an EU VAT number that is not in the VIES\ncensus is still rejected by VeriFactu after the invoice is issued.\n\nAn identifier returned in a response is the one stored. A customer saved before a rule\nexisted keeps its identifier and can still be read; issuing an invoice to it with an\nidentifier that breaks these rules is rejected with the same code, before a number is used.\n\n### Matrix of allowed combinations\n| `type`                 | `country_code = ES` | `country_code ≠ ES` |\n|------------------------|:-------------------:|:-------------------:|\n| `NIF_IVA` (02)         | ✗                   | EU member states only |\n| `PASSPORT` (03)        | ✓                   | ✓                   |\n| `COUNTRY_ID` (04)      | ✗                   | ✓                   |\n| `RESIDENCE_CERTIFICATE` (05) | ✗             | ✓                   |\n| `OTHER_DOCUMENT` (06)  | ✗                   | ✓                   |\n| `NOT_REGISTERED` (07)  | ✓                   | ✗                   |\n","properties":{"type":{"type":"string","enum":["NIF_IVA","PASSPORT","COUNTRY_ID","RESIDENCE_CERTIFICATE","OTHER_DOCUMENT","NOT_REGISTERED","02","03","04","05","06","07"],"description":"Identifier type. Use descriptive names:\n- **NIF_IVA**: EU VAT number (intra-community) — *only for an EU member state other than Spain, with that country's VAT number structure*\n- **PASSPORT**: Passport — *allowed for any country*\n- **COUNTRY_ID**: Country of residence ID — *not allowed when `country_code = ES`*\n- **RESIDENCE_CERTIFICATE**: Residence certificate — *not allowed when `country_code = ES`*\n- **OTHER_DOCUMENT**: Other supporting document — *not allowed when `country_code = ES`*\n- **NOT_REGISTERED**: Not registered in AEAT — *requires `country_code = ES`*\n\n**⚠️ DEPRECATED numeric codes** (will be removed in v2):\n02, 03, 04, 05, 06, 07 — use the descriptive names above instead.\n"},"number":{"type":"string","minLength":1,"maxLength":20,"description":"Identifier number. For `NIF_IVA`, the full EU VAT number with its country prefix\n(e.g. `FR40303265045`); see the VeriFactu rules on the parent schema.\n"},"country_code":{"type":"string","pattern":"^[A-Z]{2}$","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 code of the country that issued the document. Required except for\n`PASSPORT` and `NOT_REGISTERED`, where omitting it means `ES`. Constrains the allowed\n`type` values; see the VeriFactu rules on the parent schema.\n"}}},"CreateCustomerRequest":{"type":"object","required":["legal_name","address"],"properties":{"legal_name":{"type":"string","minLength":1,"maxLength":120,"pattern":"^\\S.*$","description":"Customer legal name (required).\n\nWhen you also send a Spanish `nif`, how it is used depends on the customer:\nfor an **individual** the AEAT census matches NIF and name together, so a\nname it does not recognise makes the customer invalid; for a **company**\nthe name is **not verified** and only the CIF decides.\n"},"trade_name":{"type":"string","maxLength":120,"nullable":true,"description":"Customer trade name. Optional, but **not empty by default**: leave it out on creation\nand it is filled with `legal_name`, which is what then shows as the recipient's trade\nname on the invoice PDF. Send it explicitly if the two differ.\n\nThe default applies **on creation only**. A `PUT` replaces the customer whole, so\nomitting `trade_name` there **clears** it instead of refilling it from `legal_name`.\n"},"nif":{"allOf":[{"$ref":"#/components/schemas/NIF"}],"description":"Spanish Tax ID (required if id_otro is not provided)"},"alternative_id":{"$ref":"#/components/schemas/AlternativeIdentifier"},"address":{"$ref":"#/components/schemas/Address"},"phone":{"$ref":"#/components/schemas/PhoneInput"},"email":{"type":"string","format":"email","minLength":5,"maxLength":255,"nullable":true,"description":"Main contact address (minimum valid email is 5 chars, e.g. a@b.co). Invoice emails go\nhere only when the customer has no `billing_emails`.\n"},"website":{"type":"string","maxLength":255,"nullable":true,"description":"Website URL","pattern":"^(https?://.+|)$"},"billing_emails":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/Email"},"description":"Addresses that receive the customer's invoice emails. When the send request names no\n`recipients` and the invoice has no `email_config` recipients, invoices go to these\naddresses, and not to `email`; `email` is used only when `billing_emails` is empty.\n"},"contact_person":{"type":"string","maxLength":200,"nullable":true,"description":"Contact person name (optional)"},"notes":{"type":"string","nullable":true,"description":"Additional notes about the customer (optional)"},"preferred_payment_method":{"$ref":"#/components/schemas/PaymentInfo"},"general_discount":{"type":"number","minimum":0,"maximum":100,"nullable":true,"description":"General discount percentage (optional)"}}},"UpdateCustomerRequest":{"type":"object","description":"Request to **replace** an existing customer (`PUT`).\n\nSend the customer complete: `trade_name`, `email`, `website`,\n`billing_emails`, `contact_person`, `notes` and `general_discount` are\n**cleared** when you leave them out of the body. To change a few fields\nand keep the rest untouched, use `PATCH /v1/customers/{customer_id}`.\n\nThe tax identifier can be corrected after creation: send `nif` OR\n`alternative_id` (never both) to replace the current one. The new\nidentifier goes through the same validation as creation (AEAT census\ncheck for Spanish NIFs, duplicate check against your other customers).\nThat census check matches `legal_name` only for individuals; for a\ncompany the name is **not verified** and only the CIF decides.\nIf neither field is sent, the current identifier is kept.\nInvoices already issued are not affected: they keep the recipient data\nsnapshotted at emission time.\n","properties":{"nif":{"allOf":[{"$ref":"#/components/schemas/NIF"}],"description":"New Spanish Tax ID for the customer. Mutually exclusive with\n`alternative_id`. Omit to keep the current identifier.\n"},"alternative_id":{"allOf":[{"$ref":"#/components/schemas/AlternativeIdentifier"}],"description":"New alternative identifier (non-Spanish customers). Mutually\nexclusive with `nif`. Omit to keep the current identifier.\n"},"legal_name":{"type":"string","minLength":1,"maxLength":120,"description":"Customer legal name. Matched against the AEAT census only for individuals;\nfor a company the name is **not verified**.\n"},"trade_name":{"type":"string","maxLength":120,"nullable":true,"description":"Customer trade name. Omitting it **clears** the field: this is a full replacement, and\nthe creation default (falling back to `legal_name`) does not apply here.\n"},"address":{"$ref":"#/components/schemas/Address"},"phone":{"$ref":"#/components/schemas/PhoneInput"},"email":{"type":"string","format":"email","minLength":5,"maxLength":255,"nullable":true,"description":"Main contact address (minimum valid email is 5 chars, e.g. a@b.co). Invoice emails go\nhere only when the customer has no `billing_emails`.\n"},"website":{"type":"string","maxLength":255,"nullable":true,"description":"Website URL","pattern":"^(https?://.+|)$"},"billing_emails":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/Email"},"description":"Addresses that receive the customer's invoice emails. When the send request names no\n`recipients` and the invoice has no `email_config` recipients, invoices go to these\naddresses, and not to `email`; `email` is used only when `billing_emails` is empty.\n"},"contact_person":{"type":"string","maxLength":200,"nullable":true,"description":"Contact person name (optional)"},"notes":{"type":"string","nullable":true,"description":"Additional notes about the customer (optional)"},"preferred_payment_method":{"$ref":"#/components/schemas/PaymentInfo"},"general_discount":{"type":"number","minimum":0,"maximum":100,"nullable":true,"description":"General discount percentage (optional)"},"active":{"type":"boolean","description":"Whether the customer is active or inactive"}}},"CsvImportResult":{"type":"object","required":["total_processed","successful","failed","duplicates_skipped"],"properties":{"total_processed":{"type":"integer","minimum":0,"description":"Total records processed from CSV","example":150},"successful":{"type":"integer","minimum":0,"description":"Number of customers successfully created","example":145},"failed":{"type":"integer","minimum":0,"description":"Number of records that failed","example":5},"duplicates_skipped":{"type":"integer","minimum":0,"description":"Number of records skipped as duplicates (same Tax ID)","example":3},"created_customers":{"type":"array","items":{"$ref":"#/components/schemas/Customer"},"description":"List of successfully created customers"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/CsvValidationError"},"description":"Error details by row"},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-critical warnings (missing optional fields)","example":["Row 2: Email not provided for customer 'TechSolutions Barcelona SA'","Row 5: Invalid phone number for customer 'María López Fernández'"]}}},"CsvValidationError":{"type":"object","required":["row","field","value","error"],"properties":{"row":{"type":"integer","minimum":1,"description":"Row number with error (1-based, excluding header)","example":12},"field":{"type":"string","description":"Field name with error","example":"nif"},"value":{"type":"string","description":"Value that caused the error","example":"12345678X"},"error":{"type":"string","description":"Detailed error message","example":"Invalid Tax ID: incorrect format"}}},"TemplateCsvInfo":{"type":"object","required":["filename","headers","example_rows"],"properties":{"filename":{"type":"string","description":"Template file name","example":"template_customers_example.csv"},"headers":{"type":"array","items":{"type":"string"},"description":"Required CSV headers. Both the English headers below and their Spanish\nequivalents are accepted by the import endpoint (the downloadable template\nuses the Spanish headers):\n`legal_name`=`nombre_fiscal`, `phone`=`telefono`,\n`address_street`=`direccion_calle`, `address_number`=`direccion_numero`,\n`address_postal_code`=`direccion_codigo_postal`,\n`address_city`=`direccion_poblacion`, `address_province`=`direccion_provincia`\n(`nif` and `email` are identical in both languages).\n","example":["legal_name","nif","email","phone","address_street","address_number","address_postal_code","address_city","address_province"]},"example_rows":{"type":"integer","description":"Number of example rows in the template","example":4},"max_records":{"type":"integer","description":"Maximum number of allowed records","example":1000},"max_file_size_mb":{"type":"number","description":"Maximum file size in MB","example":5}}},"CsvImportPreviewResult":{"type":"object","required":["metadata","customers_preview","statistics"],"description":"Complete result of a CSV import with structured preview.\nContains all parsed records, statistics and processing metadata.\n","properties":{"metadata":{"$ref":"#/components/schemas/CsvImportMetadata"},"customers_preview":{"type":"array","items":{"$ref":"#/components/schemas/CsvCustomerPreview"},"description":"Complete list of parsed customers with their validation status"},"statistics":{"$ref":"#/components/schemas/CsvImportStatistics"}}},"CsvImportMetadata":{"type":"object","required":["filename","file_size_bytes","total_rows","is_dry_run","processing_time_ms"],"description":"Metadata about the processed CSV file","properties":{"filename":{"type":"string","description":"Original file name","example":"customers_january.csv"},"file_size_bytes":{"type":"integer","description":"File size in bytes","example":1048576},"total_rows":{"type":"integer","description":"Total rows processed (excluding headers)","example":250},"is_dry_run":{"type":"boolean","description":"Whether it was a test processing without persistence","example":true},"processing_time_ms":{"type":"integer","description":"Processing time in milliseconds","example":3450}}},"CsvCustomerPreview":{"type":"object","required":["row_number","customer","status","errors","warnings"],"description":"Preview of an individual customer parsed from CSV.\nIncludes the parsed customer, validation status and specific errors/warnings.\n","properties":{"row_number":{"type":"integer","description":"Row number in CSV (starting from 1)","example":15},"customer":{"type":"object","nullable":true,"allOf":[{"$ref":"#/components/schemas/Customer"}],"description":"Parsed customer (null if there are critical parsing errors)"},"status":{"$ref":"#/components/schemas/ValidationStatus"},"errors":{"type":"array","items":{"$ref":"#/components/schemas/CsvValidationError"},"description":"List of validation errors for this customer"},"warnings":{"type":"array","items":{"type":"string"},"description":"List of warnings (customer processable but with observations)","example":["Row 15: Email not provided for customer 'Empresa ABC'"]}}},"ValidationStatus":{"type":"string","enum":["VALID","WARNING","ERROR","DUPLICATE","NIF_INVALID"],"description":"Validation status of the parsed customer:\n- VALID: Valid customer, ready to import\n- WARNING: Valid customer but with non-critical warnings\n- ERROR: Invalid customer, cannot be processed\n- DUPLICATE: Duplicate customer (in CSV or database)\n- NIF_INVALID: Tax ID not valid according to AEAT registry (VeriFactu)\n"},"CsvImportStatistics":{"type":"object","required":["total_processed","successful","with_warnings","failed","duplicates","invalid_nifs","success_rate"],"description":"Calculated statistics from CSV processing","properties":{"total_processed":{"type":"integer","description":"Total records processed","example":250},"successful":{"type":"integer","description":"Valid customers without warnings","example":180},"with_warnings":{"type":"integer","description":"Valid customers but with warnings","example":45},"failed":{"type":"integer","description":"Customers with validation errors","example":15},"duplicates":{"type":"integer","description":"Duplicate customers (in CSV or DB)","example":8},"invalid_nifs":{"type":"integer","description":"Customers with invalid Tax IDs in AEAT","example":2},"success_rate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Success rate (successful + with_warnings) / total","example":0.9}}},"Product":{"type":"object","required":["id","name","category","main_tax","active","created_at","updated_at"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique UUID of the product/service","example":"550e8400-e29b-41d4-a716-446655440000"},"code":{"type":"string","maxLength":50,"pattern":"^[a-zA-Z0-9_-]*$","nullable":true,"description":"Unique alphanumeric product code (optional)","example":"SERV-001"},"name":{"type":"string","maxLength":255,"description":"Product/service name","example":"Technical consulting"},"description":{"type":"string","description":"Detailed product/service description (optional)","example":"Specialized technical consulting services in web development"},"category":{"$ref":"#/components/schemas/ProductCategory"},"default_price":{"type":"number","minimum":0,"multipleOf":0.0001,"description":"Suggested default price (optional)","example":85.5},"unit":{"type":"string","maxLength":50,"description":"Unit of measure (optional)","example":"hours"},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"Equivalence surcharge percentage (optional)","example":5.2},"irpf_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"IRPF withholding percentage (optional)","example":15},"active":{"type":"boolean","description":"Indicates whether the product is active","example":true},"created_at":{"type":"string","format":"date-time","description":"Creation date and time","example":"2025-01-18T10:30:00Z"},"updated_at":{"type":"string","format":"date-time","description":"Last update date and time","example":"2025-01-18T15:45:30Z"}}},"ProductCategory":{"type":"string","enum":["PRODUCT","SERVICE","CONSULTING","SOFTWARE","TRAINING","OTHER"],"description":"Product/service category:\n* PRODUCT - Physical, tangible products\n* SERVICE - General services\n* CONSULTING - Consulting and advisory services\n* SOFTWARE - Development, licenses, SaaS\n* TRAINING - Courses, workshops, training\n* OTHER - Other unclassified types\n","example":"CONSULTING"},"CreateProductRequest":{"type":"object","required":["name"],"properties":{"code":{"type":"string","maxLength":50,"pattern":"^[a-zA-Z0-9_-]*$","nullable":true,"description":"Unique alphanumeric product code (optional)","example":"SERV-001"},"name":{"type":"string","maxLength":255,"description":"Product/service name","example":"Technical consulting"},"description":{"type":"string","description":"Detailed description (optional)","example":"Specialized technical consulting services"},"category":{"$ref":"#/components/schemas/ProductCategory"},"default_price":{"type":"number","minimum":0,"multipleOf":0.0001,"description":"Suggested default price (optional)","example":85.5},"unit":{"type":"string","maxLength":50,"description":"Unit of measure (optional)","example":"hours"},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"Equivalence surcharge percentage (optional).\n\nMust be coherent with `main_tax.regime_key`: a surcharge > 0 is\nonly valid under regime `18`. When `regime_key` is omitted it is\nderived automatically (`18` with a surcharge > 0, `01` otherwise).\nAn explicit `regime_key` that does not admit a surcharge combined\nwith a surcharge > 0 is rejected with a 422\n(`SURCHARGE_REQUIRES_REGIME`), and an explicit regime `18` without\na surcharge > 0 is rejected with a 422\n(`REGIME_REQUIRES_SURCHARGE`).\n","example":5.2},"irpf_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"IRPF withholding percentage (optional).\n\nThe rate must be one of `IrpfPercentage` (`INVALID_IRPF` otherwise) and one\nthe company can bear, the same check as an invoice line (see\n`WithholdingOptions` in the tax configuration): an entity gets the rates of\nindividuals rejected with `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`, and an individual gets\n`9.5` rejected with `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`.\n","example":15}}},"UpdateProductRequest":{"type":"object","description":"Request to **replace** an existing product (`PUT`).\n\nSend the product complete: leaving out `main_tax`,\n`equivalence_surcharge_rate` or `irpf_rate` resets them to the creation\ndefaults (IVA 21%, 0% and 0%), it does **not** keep the current values.\nTo change a few fields and keep the rest untouched, use\n`PATCH /v1/products/{product_id}`.\n","properties":{"code":{"type":"string","maxLength":50,"pattern":"^[a-zA-Z0-9_-]*$","nullable":true,"description":"Unique alphanumeric product code (optional)","example":"SERV-001"},"name":{"type":"string","maxLength":255,"description":"Product/service name","example":"Technical consulting"},"description":{"type":"string","description":"Detailed description (optional)","example":"Specialized technical consulting services"},"category":{"$ref":"#/components/schemas/ProductCategory"},"default_price":{"type":"number","minimum":0,"multipleOf":0.0001,"description":"Suggested default price (optional)","example":85.5},"unit":{"type":"string","maxLength":50,"description":"Unit of measure (optional)","example":"hours"},"main_tax":{"$ref":"#/components/schemas/TaxInfo"},"equivalence_surcharge_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"Equivalence surcharge percentage (optional).\n\nMust be coherent with `main_tax.regime_key`: a surcharge > 0 is\nonly valid under regime `18`. When `regime_key` is omitted it is\nderived automatically (`18` with a surcharge > 0, `01` otherwise).\nAn explicit `regime_key` that does not admit a surcharge combined\nwith a surcharge > 0 is rejected with a 422\n(`SURCHARGE_REQUIRES_REGIME`), and an explicit regime `18` without\na surcharge > 0 is rejected with a 422\n(`REGIME_REQUIRES_SURCHARGE`).\n","example":5.2},"irpf_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"description":"IRPF withholding percentage (optional).\n\nThe rate must be one of `IrpfPercentage` (`INVALID_IRPF` otherwise) and one\nthe company can bear, the same check as an invoice line (see\n`WithholdingOptions` in the tax configuration): an entity gets the rates of\nindividuals rejected with `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`, and an individual gets\n`9.5` rejected with `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`.\n","example":15},"active":{"type":"boolean","description":"Indicates whether the product is active","example":true}}},"ProductResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Product"}}}]},"ProductsListResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}]},"ProductQueryParams":{"type":"object","properties":{"category":{"$ref":"#/components/schemas/ProductCategory"},"active":{"type":"boolean","description":"Filter by active/inactive status","example":true},"search":{"type":"string","maxLength":100,"description":"Search in name, description or code","example":"consulting"},"page":{"type":"integer","minimum":0,"default":0,"description":"Page number (0-indexed)","example":0},"size":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Page size","example":20}}},"SeriesFormat":{"type":"string","minLength":1,"maxLength":255,"pattern":"^[A-Z0-9\\-_/{}:]*$","example":"{CODIGO}-{YYYY}-{NUM:4}","description":"Format template with available variables (UPPERCASE ONLY):\n- {CODIGO}: Series code (e.g., \"FAC\")\n- {YYYY}: Year with 4 digits (e.g., \"2025\")\n- {YY}: Year with 2 digits (e.g., \"25\")\n- {MM}: Month with 2 digits (e.g., \"01\")\n- {NUM}: Sequential number without padding (e.g., \"1\")\n- {NUM:X}: Sequential number with padding (e.g., {NUM:4} → \"0001\")\n\n**REQUIRED**: Must contain at least {NUM} or {NUM:X}\n**IMPORTANT**: Only uppercase (rejects {yy}, {mm}, {codigo}, etc.)\n\nValid examples:\n- \"{CODIGO}-{YYYY}-{NUM:4}\" → \"FAC-2025-0001\"\n- \"{CODIGO}/{NUM:6}\" → \"FAC/000001\"\n- \"{YYYY}{MM}-{NUM:3}\" → \"202501-001\"\n\nThe generated number is the invoice number sent to the AEAT, which accepts at most 60\nprintable ASCII characters and none of `\"`, `'`, `<`, `>`, `=`. A format whose longest\npossible number breaks that rule is rejected with `422 SERIES_FORMAT_NUMBER_TOO_LONG` or\n`SERIES_FORMAT_INVALID_CHARACTERS`. The counter counts as at least 9 digits, with or without\npadding: `{NUM:X}` is a minimum width, not a maximum.\n"},"CounterReset":{"type":"string","enum":["NEVER","ANNUAL","MONTHLY"],"description":"Counter reset policy:\n- NEVER: Counter never resets (continuous numbering)\n- ANNUAL: Counter resets yearly\n- MONTHLY: Counter resets monthly\n"},"SeriesCode":{"type":"string","pattern":"^[A-Z0-9\\-_]{1,50}$","minLength":1,"maxLength":50,"example":"FAC","description":"Alphanumeric series code (used in {CODIGO} variable).\nAllows uppercase letters, numbers, hyphens and underscores.\n"},"VeriFactuConfiguration":{"type":"object","description":"VeriFactu configuration as resolved by the server. **Response only** — the update body is\n`UpdateVeriFactuConfigurationRequest`, which carries just `enabled`.\n","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Whether this tax ID is under the VeriFactu regime in the environment of the request:\nwhile it is on, every invoice of this tax ID is registered with the AEAT; while it is\noff, none is. Always `true` in sandbox.\n"},"nif_status":{"type":"string","nullable":true,"enum":["ACTIVATED","DEACTIVATED"],"description":"Whether the tax ID is registered for VeriFactu submission in this environment\n(null if VeriFactu was never enabled):\n- ACTIVATED: registered and ready to submit\n- DEACTIVATED: not registered, or deregistered (data retained for 30 days)\n\n**In Live**, `enabled: true` implies `ACTIVATED`: enabling *is* registering, atomically.\n**In sandbox that implication does not hold**: `enabled` is always `true` there — it is a\nconstant of the mode, not a decision — while the registration travels asynchronously, so\n`nif_status` can still be `DEACTIVATED` or null for a while. The former `PENDING` and\n`ERROR` values described an in-between state that no longer exists.\n"},"nif_registered_at":{"type":"string","format":"date-time","nullable":true,"description":"Successful activation date in VeriFactu production"},"status":{"type":"string","nullable":true,"enum":["DISABLED","UNSIGNED","NOT_ACTIVATED","ACTIVE","ERROR"],"description":"Single derived VeriFactu state for the (NIF, environment) of the\nrequest — the ONE truth every UI surface (settings banner, activation\ncard, invoice form) reads, so they can no longer contradict each other.\nRead-only; resolved server-side. Null only if never configured.\n- DISABLED: VeriFactu not enabled for this account.\n- UNSIGNED: enabled but the NIF has no signed AEAT representation.\n- NOT_ACTIVATED: signed, but no API key for the operating environment.\n- ACTIVE: signed and activated with API key: ready to submit.\n- ERROR: historical, no longer reachable. Activation is atomic: a failed\n  activation leaves nothing persisted and returns the reason, so no row ever rests in\n  this state. Kept in the enum so old clients keep parsing stored values.\n"},"signed":{"type":"boolean","nullable":true,"description":"Secondary flag: whether the NIF has a signed AEAT representation\n(per-NIF, shared across environments). Data for the wizard, NOT the gate.\n"},"activated":{"type":"boolean","nullable":true,"description":"Secondary flag: whether the NIF is activated (has an API key) in the\noperating environment (per-environment). Data, NOT the gate.\n"},"pdf_generated":{"type":"boolean","nullable":true,"description":"Secondary flag: whether a representation PDF artifact exists. Used by\nthe signature wizard as secondary data, NOT the source of truth of the gate.\n"}}},"UpdateVeriFactuConfigurationRequest":{"type":"object","description":"Request body of `PUT /v1/configuration/verifactu`. Carries the **only writable field**;\neverything else in `VeriFactuConfiguration` is resolved server-side.\n\n`enabled` is required and has **no default**: this PUT replaces the whole state, so\nomitting it is a client error, not a silent `false`. A `default:` here would be\nmaterialized in the generated DTO and would satisfy the `@NotNull` before validation\ncould tell \"not sent\" from \"sent as false\".\n","required":["enabled"],"properties":{"enabled":{"type":"boolean","description":"Whether this tax ID is under the VeriFactu regime in the environment of the request.\n\nIt is a fact about the **taxpayer**, not about a document: while it is on, *every*\ninvoice of this tax ID is registered with the AEAT; while it is off, none is. There\nis no per-invoice choice.\n\nTurning it on **registers the tax ID for VeriFactu submission in the same call**,\natomically: if the registration is refused, nothing is persisted and the response\ncarries the reason.\nIn Live it requires a signed and validated AEAT representation first\n(`VERIFACTU_REPRESENTATION_REQUIRED`).\n\nIn **sandbox VeriFactu is always on and cannot be turned off**\n(`VERIFACTU_ALWAYS_ON_IN_SANDBOX`): nothing there reaches the real AEAT.\n"}}},"TaxConfiguration":{"type":"object","required":["default_main_tax","apply_equivalence_surcharge","apply_irpf"],"properties":{"default_main_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"},{"description":"Convenience **prefill**, not a default the API applies.\nIncludes tax regime type (IVA/IGIC/IPSI/OTHER), percentage and regime key.\n\nIt seeds the tax fields of a line where a human (or BeeL itself) is composing\none: the dashboard invoice editor, onboarding, and the drafts BeeL generates\nfrom payment events. The value is written into the line at that moment and is\nvisible and editable from then on.\n\n**Never applied to invoice lines created via the API**: omitting `main_tax` on a\n`NORMAL` line is a validation error (`422 LINE_MAIN_TAX_REQUIRED`), never a\nsilent fallback to this value. Which tax a line bears is a fiscal decision of\nthe issuer, so the API refuses to guess it — the same line body must produce the\nsame invoice regardless of how the account happens to be configured today.\n(`SUPLIDO` lines carry no main tax at all, so nothing is prefilled there either.)\n\nCommon examples:\n- Standard VAT: { type: \"IVA\", percentage: 21, regime_key: \"01\" }\n- Special Reduced IGIC: { type: \"IGIC\", percentage: 7, regime_key: \"01\" }\n- Standard IPSI: { type: \"IPSI\", percentage: 10, regime_key: \"01\" }\n"},{"default":{"type":"IVA","percentage":21,"regime_key":"01"}}]},"default_exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"default_exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text. Only used when `default_exemption_reason` is `OTRO`,\nwhere it is mandatory — same rule as in the invoice line.\n"},"apply_equivalence_surcharge":{"type":"boolean","default":false,"description":"Whether the freelancer is under the equivalence surcharge regime.\n\n**Business rule**: If `true`, the `default_equivalence_surcharge` field is REQUIRED.\n"},"default_equivalence_surcharge":{"type":"number","example":5.2,"description":"Default equivalence surcharge, as the configuration holds it. It is what is stored,\nnot what a request accepts (see `EquivalenceSurchargePercentage`): a configuration\nsaved with the `0.625` surcharge of VAT at 5 %, before it was corrected to 0.62,\nis returned as `0.625` until it is updated.\n\n**Business rule**: REQUIRED if `apply_equivalence_surcharge` is `true`.\n"},"apply_irpf":{"type":"boolean","default":false,"description":"Whether IRPF withholding should be applied to invoices.\n\nDefaults to `false`: no withholding is applied until the user declares one\n(a rate nobody declared is never invented).\n\n**Business rule**: If `true` and `irpf_exempt` is `false`,\nthe `default_irpf_rate` field is REQUIRED.\n"},"default_irpf_rate":{"type":"number","example":15,"description":"Default IRPF for new invoices, as the configuration holds it. It is what is stored,\nnot what a request accepts (see `IrpfPercentage`): a configuration saved with a rate\nthe table no longer has is returned as it is until it is updated.\n\n**Business rule**: REQUIRED if `apply_irpf` is `true` and `irpf_exempt` is `false`.\n"},"irpf_exempt":{"type":"boolean","default":false,"description":"Whether the freelancer is exempt from IRPF withholding (e.g., business start).\n\n**Note**: When `true`, any provided `default_irpf_rate` is saved but not applied to invoices.\nThis allows pre-configuring the rate for when the exemption ends.\n"},"default_payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"default":"BANK_TRANSFER","description":"Default payment method for new invoices.\nIf NONE is selected, no payment information will be shown on the invoice.\n"},"payment_term_days":{"type":"integer","nullable":true,"minimum":0,"maximum":365,"description":"Default payment term in days (0-365). Null means no default term."},"proforma_validity_days":{"type":"integer","nullable":true,"minimum":0,"maximum":365,"description":"Default validity term in days for new proformas (0-365). Null means no default:\nproformas are created without an expiry date.\n\nUI-only default: the dashboard uses it to prefill `valid_until` when creating a\nproforma (issue date + N days, editable). It is never applied server-side, so the\npublic API contract of `valid_until` does not change.\n"},"withholding_options":{"allOf":[{"$ref":"#/components/schemas/WithholdingOptions"}],"readOnly":true,"description":"The withholding (IRPF) rates this company can put on its invoices, deduced from its\nNIF, and the one suggested by default. Read-only: it is ignored if sent.\n\nUse it to build the IRPF picker of this company instead of the whole list of\n`GET /v1/tax-types`, which is the same for everyone.\n"}}},"InvoiceSeries":{"type":"object","required":["id","name","code","format","counter_reset","active","default_series","document_type"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique series ID"},"document_type":{"$ref":"#/components/schemas/DocumentType"},"name":{"type":"string","minLength":1,"maxLength":100,"example":"Main Series","description":"Descriptive name of the series"},"code":{"$ref":"#/components/schemas/SeriesCode"},"description":{"type":"string","maxLength":1000,"example":"Series for standard invoices","description":"Optional series description"},"format":{"$ref":"#/components/schemas/SeriesFormat"},"counter_reset":{"$ref":"#/components/schemas/CounterReset"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"default":1,"example":1,"description":"Initial number configured for this series counter: the number the series starts from.\nIt applies only to the first period in which the series issues an invoice: with\n`counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With\n`NEVER` there is a single period, so numbering simply continues from it.\n"},"active":{"type":"boolean","default":true,"description":"Indicates whether the series is active"},"default_series":{"type":"boolean","default":false,"description":"Indicates whether this is the user's default series (only one can be)"},"numbering_locked":{"type":"boolean","readOnly":true,"description":"`true` once this series has issued at least one invoice. From that point the\nfields that drive numbering (`code`, `format`, `counter_reset`, `initial_number`)\nare permanently locked and any attempt to change them is rejected — numbering can\nonly be adjusted **before** the first invoice of a series. Cosmetic fields\n(`name`, `description`) remain editable. To use a different numbering, create a\nnew series.\n"},"created_at":{"type":"string","format":"date-time","description":"Creation date"},"next_number":{"type":"integer","format":"int64","readOnly":true,"description":"Next invoice number that will be assigned for this series"},"updated_at":{"type":"string","format":"date-time","description":"Last update date"}}},"TaxRegime":{"type":"object","required":["code","name","description","tax_rates","applies_equivalence_surcharge","regime_keys"],"properties":{"code":{"type":"string","description":"Tax regime code.\n\n- `IVA`: Value Added Tax (Iberian Peninsula and Balearic Islands).\n- `IGIC`: Canary Islands General Indirect Tax.\n- `IPSI`: Production, Services and Import Tax (Ceuta and Melilla).\n- `OTHER`: special regimes outside the three above.\n","enum":["IVA","IGIC","IPSI","OTHER"],"example":"IVA"},"name":{"type":"string","description":"Tax regime name","example":"IVA"},"description":{"type":"string","description":"Detailed description of the tax regime","example":"Value Added Tax"},"tax_rates":{"type":"array","items":{"$ref":"#/components/schemas/TaxPercentage"},"description":"Available percentages in this regime"},"applies_equivalence_surcharge":{"type":"boolean","description":"Whether this regime applies equivalence surcharge"},"regime_keys":{"type":"array","items":{"$ref":"#/components/schemas/VeriFactuRegimeKey"},"description":"Valid VeriFactu regime keys for this tax type"}}},"VeriFactuRegimeKey":{"type":"object","required":["code","description"],"properties":{"code":{"type":"string","description":"VeriFactu regime code","pattern":"^[0-9]{2}$","example":"01"},"description":{"type":"string","description":"Regime key description","example":"General regime operation"}}},"TaxPercentage":{"type":"object","required":["percentage","description","active"],"properties":{"percentage":{"type":"number","description":"Tax percentage","example":21},"description":{"type":"string","description":"Tax type description","example":"IVA (21%)"},"active":{"type":"boolean","description":"Indicates whether the type is active","default":true},"associated_equivalence_surcharge":{"type":"number","description":"Associated equivalence surcharge percentage (only for VAT)","minimum":0,"example":5.2,"nullable":true},"valid_from":{"type":"string","format":"date","nullable":true,"description":"First operation date on which AEAT accepts this rate; `null` when it has no start.\nThe date judged is the invoice's `operation_date`, or its issue date when it has none.\n","example":null},"valid_until":{"type":"string","format":"date","nullable":true,"description":"Last operation date on which AEAT accepts this rate; `null` when it has no end.\nA rate with a `valid_until` in the past only fits invoices for operations of its period.\n","example":null}}},"VatType":{"type":"object","required":["percentage","description","verifactu_code","active"],"properties":{"percentage":{"type":"number","description":"VAT percentage","example":21},"description":{"type":"string","description":"VAT type description","example":"IVA General (21%)"},"verifactu_code":{"type":"string","description":"VAT type code according to VeriFactu regulations","example":"S1"},"active":{"type":"boolean","description":"Whether the VAT type is active","default":true}}},"IrpfType":{"type":"object","required":["percentage","description","active"],"properties":{"percentage":{"type":"number","description":"IRPF withholding percentage","example":15},"description":{"type":"string","description":"IRPF type description","example":"IRPF Profesional (15%)"},"active":{"type":"boolean","description":"Whether the IRPF type is active","default":true}}},"EquivalenceSurcharge":{"type":"object","description":"One VAT ↔ surcharge pair AEAT accepts, plus the `0` entry for \"no surcharge\". The same\nsurcharge can appear twice with different VAT rates and periods (0.5 goes with 4 % always\nand with 5 % up to 2022-12-31).\n","required":["percentage","associated_vat","description","active"],"properties":{"percentage":{"type":"number","description":"Equivalence surcharge percentage","example":5.2},"associated_vat":{"type":"number","description":"VAT percentage to which this surcharge is associated","example":21},"description":{"type":"string","description":"Equivalence surcharge description","example":"RE 5.2% (IVA 21%)"},"active":{"type":"boolean","description":"Whether the surcharge is active","default":true},"valid_from":{"type":"string","format":"date","nullable":true,"description":"First operation date on which AEAT accepts this VAT ↔ surcharge pair; `null` when it has no start.\nThe date judged is the invoice's `operation_date`, or its issue date when it has none.\n","example":null},"valid_until":{"type":"string","format":"date","nullable":true,"description":"Last operation date on which AEAT accepts this VAT ↔ surcharge pair; `null` when it has no end.\nA rate with a `valid_until` in the past only fits invoices for operations of its period.\n","example":null}}},"CreateSeriesRequest":{"type":"object","description":"Request to create an invoice series. `document_type` is required: a new series always has\na type, and `UNASSIGNED` is rejected with `422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`.\nCorrective invoices need a series of their own (`CORRECTIVE`).\n","required":["document_type","name","code","format"],"properties":{"document_type":{"$ref":"#/components/schemas/DocumentType"},"name":{"type":"string","minLength":1,"maxLength":100,"example":"Main Series","description":"Descriptive name of the series"},"code":{"$ref":"#/components/schemas/SeriesCode"},"description":{"type":"string","maxLength":1000,"nullable":true,"example":"Series for standard invoices","description":"Optional series description"},"format":{"$ref":"#/components/schemas/SeriesFormat"},"counter_reset":{"allOf":[{"$ref":"#/components/schemas/CounterReset"}],"default":"ANNUAL","description":"When this series' counter resets. Defaults to `ANNUAL` when omitted — so a format\nwithout a year token must come with `counter_reset: NEVER`.\n"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"default":1,"example":1,"description":"Initial number for this series counter.\nUseful when migrating from another system and wanting to continue existing numbering.\nFor example, if the last invoices were 2024-0150, you can set initial_number=151.\nDefault value is 1.\nIt applies only to the first period in which the series issues an invoice: with\n`counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With\n`NEVER` there is a single period, so numbering simply continues from it.\n"},"active":{"type":"boolean","default":true,"description":"Whether the series is active"},"default_series":{"type":"boolean","default":false,"description":"Whether this is the default series for its document_type.\n\n**Auto-promotion:** the domain guarantees that, while at least one\nseries exists for a given `(document_type, environment)`, exactly one\nof them is the default. So if you create the **first** series of a\n`document_type` (no default exists yet for that type and environment),\nit is marked as default **even if you send `false`** — the response\nwill then return `default_series: true`. Send `true` to also unmark\nthe current default of that type.\n"}}},"UpdateSeriesRequest":{"type":"object","description":"Request to **update** an invoice series (`PUT`). Despite the verb, this is a merge\nfield by field, not a wholesale replacement: **absent fields keep their current\nvalue**, they are never reset to a default.\n\n**IMPORTANT:** the fields that drive numbering (`code`, `format`, `counter_reset`,\n`initial_number`) and `document_type` can only be modified while the series **has NO\nassociated invoices** (`document_type` also when the series is `UNASSIGNED`); the rest\n(`name`, `description`, `active`, `default_series`) can always be changed.\n\nMoving a series to `document_type: UNASSIGNED` is rejected with\n`422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`; a series that already is `UNASSIGNED` can be\nedited without changing its type.\n\nThe one thing this route cannot do is *clear* a field: an explicit\n`description: null` is ignored here. To clear the description, use\n`PATCH /v1/configuration/series/{series_id}`.\n","properties":{"document_type":{"$ref":"#/components/schemas/DocumentType"},"name":{"type":"string","minLength":1,"maxLength":100,"example":"Main Series","description":"Descriptive name of the series"},"code":{"$ref":"#/components/schemas/SeriesCode"},"description":{"type":"string","maxLength":1000,"nullable":true,"example":"Series for standard invoices","description":"Optional series description"},"format":{"$ref":"#/components/schemas/SeriesFormat"},"counter_reset":{"$ref":"#/components/schemas/CounterReset"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"example":54,"description":"Initial number for this series counter.\nUseful for adjusting numbering before the series issues its first invoice.\nFor example, if you want the next invoice to be 2025-0054, set initial_number=54.\nIt applies only to the first period in which the series issues an invoice: with\n`counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With\n`NEVER` there is a single period, so numbering simply continues from it.\n"},"active":{"type":"boolean","description":"Whether the series is active.\n\n**Restriction:** A default series cannot be deactivated\n(another must be set as default first).\n"},"default_series":{"type":"boolean","description":"Whether this is the default series.\n\n**Restriction:** An inactive series cannot be marked as default.\n"}}},"UpdateTaxConfigurationRequest":{"type":"object","properties":{"default_main_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"}],"description":"Default main tax configuration.\nIf provided, completely replaces the current configuration.\n\nIt travels together with `default_exemption_reason`: sending the tax without a\nreason clears the stored one (going back from 0% to 21% cannot leave an orphan\n\"art. 20 exempt\" behind), and sending only the reason applies it to the tax\nalready stored. Sending neither leaves the current declaration untouched.\n"},"default_exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"default_exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text, mandatory when `default_exemption_reason` is `OTRO`.\n\nOnly `EXENTA_ART_20` and `OTRO` can be declared as a default — the reasons a\nNIF can verify on its own. The rest depend on the recipient, the operation or\nthe regime, so they are declared per invoice line; sending one returns 422.\nA 0% VAT/IPSI without a reason is also rejected with 422: in those taxes 0% is\nnot a rate, it is the sentinel of an operation carrying no tax.\n"},"apply_equivalence_surcharge":{"type":"boolean","description":"Whether the freelancer is under the equivalence surcharge regime.\n\nOmit it to leave the current value untouched. On creation, omitting it means `false`.\n"},"default_equivalence_surcharge":{"$ref":"#/components/schemas/EquivalenceSurchargePercentage"},"apply_irpf":{"type":"boolean","description":"Whether IRPF withholding should be applied.\n\nOmit it to leave the current value untouched. On creation, omitting it means `false`:\na withholding nobody declared is not applied.\n"},"default_irpf_rate":{"$ref":"#/components/schemas/IrpfPercentage"},"irpf_exempt":{"type":"boolean","description":"Whether the freelancer is exempt from IRPF withholding.\n\nOmit it to leave the current value untouched. On creation, omitting it means `false`.\n"},"default_payment_method":{"type":"string","nullable":true,"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"description":"Default payment method for new invoices.\nIf NONE is selected, no payment information will be shown on the invoice.\n"},"payment_term_days":{"type":"integer","nullable":true,"minimum":0,"maximum":365,"description":"Default payment term in days (0-365). Omit it to leave the current value untouched;\nsend `null` to clear it.\n"},"proforma_validity_days":{"type":"integer","nullable":true,"minimum":0,"maximum":365,"description":"Default validity term in days for new proformas (0-365). Omit it to leave the current\nvalue untouched; send `null` to clear it (proformas stop getting a prefilled expiry date).\n"}}},"ValidateNifRequest":{"type":"object","required":["nif"],"properties":{"nif":{"allOf":[{"$ref":"#/components/schemas/NIF"},{"description":"NIF/CIF to validate against the AEAT census.\nMust be 9 alphanumeric characters.\n"}]},"legal_name":{"type":"string","nullable":true,"minLength":1,"maxLength":255,"description":"Last name and first name (individual) or business name (legal entity).\n\n**Important**:\n- REQUIRED for individuals (NIFs starting with a number)\n- OPTIONAL for legal entities (NIFs starting with a letter)\n\nFor an **individual**, AEAT matches NIF and name together: send a name the\ncensus does not recognise and the person is not identified, so the status\ncomes back `INVALID`.\n\nFor a **legal entity**, the name is **not verified**. AEAT identifies a\ncompany by its CIF alone — there is no census answer meaning \"this name is\nwrong\", so validation depends only on the CIF and any name you send is\naccepted. Use the `legal_name` of the response to contrast your own.\n","example":"JUAN PEREZ GARCIA"}}},"ValidateNifResponse":{"type":"object","required":["valid","status","message"],"properties":{"valid":{"type":"boolean","description":"true if the NIF is in the AEAT census, **is active**, and — for an\nindividual — the name matches (status = VALID). false otherwise.\n\nFor a legal entity the name is **not verified**: AEAT identifies a\ncompany by its CIF alone, so `valid: true` says nothing about the name\nyou sent. See `legal_name_verified`.\n\nBeing in the census is not enough: a deregistered or revoked NIF comes\nback `false`. Read `census_status` to tell those apart from a NIF that\nis simply not registered, because they are not fixed the same way.\n\nA **deregistered** NIF cannot issue invoices, but it can still receive\nthem — a company closing down does not stop you from invoicing what\naccrued before it closed, and BeeL still accepts it as a recipient. A\n**revoked** NIF cannot operate at all: it is rejected on both sides.\n","example":true},"status":{"allOf":[{"$ref":"#/components/schemas/NifValidationStatus"},{"description":"Validation status"}]},"legal_name":{"type":"string","nullable":true,"description":"Legal name as it appears in the AEAT census — never an echo of the name\nyou sent. Only available if status = VALID.\n\nFor a legal entity this is the only signal that your own name is wrong,\nsince the one you send is not checked: comparing the two is up to you.\n\nDeliberately withheld when an individual is not identified because the\nname you sent is close but wrong (`census_status` =\n`NOT_IDENTIFIED_SIMILAR`). AEAT does return the real name there, but\nyou have just shown you do not know it.\n","example":"JUAN PEREZ GARCIA"},"legal_name_verified":{"type":"boolean","description":"Whether AEAT actually cross-checked the `legal_name` you sent.\n\n`true` only for an **individual** that was identified: AEAT matches NIF\nand name together there, so an identification is the check having\npassed.\n\nAlways `false` for a **legal entity** — its name is **not verified**.\nAEAT identifies a company by its CIF alone and returns the census name\nwithout looking at the one you sent, so `valid: true` says nothing about\nyour name. Comparing it against `legal_name` is up to you.\n\nAlso `false` whenever the census was not reached at all (`PENDING`,\n`ERROR`, or a syntactically invalid NIF).\n","example":false},"census_status":{"allOf":[{"$ref":"#/components/schemas/CensusStatus"},{"description":"Census situation AEAT reported for this NIF. Absent when the census\nwas not reached (`PENDING`, `ERROR`, or a syntactically invalid NIF)\n— absent is not \"not registered\".\n"}],"example":"IDENTIFIED"},"message":{"type":"string","description":"Descriptive message of the validation result, in the language of the request\n(`Accept-Language`: `es`, `en` or `ca`). Branch on `status` and `census_status`, not on\nthis text. For a legal entity the name is not verified: AEAT identifies a company by\nits CIF alone.\n\nExamples (English):\n- VALID: \"NIF successfully validated against the AEAT census\"\n- INVALID: \"The NIF is not in the AEAT census or, for an individual, the name does not match\"\n- PENDING: \"NIF validation is pending. The system will validate it automatically when the service is available.\"\n- ERROR: \"The NIF could not be validated because of a technical error. Try again later.\"\n","example":"NIF successfully validated against the AEAT census"},"validated_at":{"type":"string","format":"date-time","nullable":true,"description":"Timestamp of when the NIF was successfully validated.\nOnly available if status = VALID.\n","example":"2025-01-15T10:30:00Z"}}},"InvoiceTemplateOption":{"type":"object","required":["code","name","description"],"properties":{"code":{"allOf":[{"$ref":"#/components/schemas/InvoiceTemplateType"}],"description":"The value to send as `template_type`.","example":"MODERN_TABLE"},"name":{"type":"string","description":"Template name, translated. Meant to be shown to a person.","example":"Modern Table"},"description":{"type":"string","description":"What the template is suited to, translated. One or two sentences.","example":"Structured table design, suited to standard products and services with several lines."}}},"InvoiceCustomizationOptionsResponse":{"type":"object","required":["template_types"],"properties":{"template_types":{"type":"array","description":"Templates available to render the invoices of a NIF.","items":{"$ref":"#/components/schemas/InvoiceTemplateOption"}}}},"TaxType":{"type":"string","enum":["IVA","IGIC","IPSI","OTHER"],"description":"Tax type by territory:\n- IVA: Iberian Peninsula and Balearic Islands (4%, 10%, 21%; 2%, 5% and 7.5% only on operations of their period)\n- IGIC: Canary Islands (0%, 3%, 5%, 7%, 9.5%, 15%, 20%)\n- IPSI: Ceuta and Melilla (0.5%, 1%, 2%, 4%, 8%, 10%)\n- OTHER: Configurable 0%-100%\n\nUnder IVA and IPSI, 0% is not one of these rates: it is the exemption/non-subject\nsentinel and always travels with an `exemption_reason`. IGIC's 0% is a real rate.\nSee `TaxInfo` for the full rules.\n","example":"IVA"},"RegimeKey":{"type":"string","enum":["01","02","03","04","05","06","07","08","09","10","11","14","15","17","18","19","20"],"description":"Regime key according to VeriFactu regulations. Omitted, `01` (general regime) applies:\n- 01: General regime operation\n- 02: Export (IVA and IGIC; not IPSI, whose AEAT list is `01, 08, 11, 18, 19, 20`)\n- 03: Used goods, art, antiques (not accepted, see below)\n- 04: Investment gold\n- 05: Travel agencies\n- 06: Group of entities (not accepted, see below)\n- 07: Cash basis\n- 08: Operation subject to another indirect tax — IPSI or IGIC on an IVA line, IPSI or IVA\n  on an IGIC line. It is **not** the general regime of IGIC, which is `01`.\n- 09: Mediating agencies\n- 10: Third-party collections\n- 11: Local rental\n- 14: VAT pending in certifications (not accepted, see below)\n- 15: VAT pending successive tract\n- 17: OSS and IOSS\n- 18: Equivalence surcharge\n- 19: REAGYP\n- 20: Simplified regime\n\n**What AEAT requires with each key** (Validaciones VERI*FACTU 3.1.3.15.6), checked on\nIVA and IGIC lines before the invoice is numbered. Otherwise the request is rejected with\n`422` and the code in brackets:\n- `04`: only reverse charge (an `ISP_ART_84_2_*` reason) or an exemption\n  (`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`).\n- `08`: only `exemption_reason: NO_SUJETA_LOCALIZACION`, at 0 %\n  (`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`).\n- `10`: only `exemption_reason: NO_SUJETA_ART_7_9`, on a `STANDARD` invoice whose\n  recipient has a `nif` (`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`,\n  `REGIME_KEY_REQUIRES_STANDARD_INVOICE`, `REGIME_KEY_REQUIRES_RECIPIENT_NIF`).\n- `11` (IVA): a subject line only at 21 %, and no reverse charge\n  (`REGIME_KEY_REQUIRES_VAT_RATE`, `REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`).\n- `06` and `14` are not accepted (`REGIME_KEY_NOT_SUPPORTED`): AEAT requires with them\n  data the invoice does not carry (a cost-based taxable base; an operation date after the\n  issue date and a public-administration recipient).\n- `03` (used goods) is not accepted (`REGIME_KEY_NOT_SUPPORTED`): under it the invoice\n  must not show the tax separately (RD 1619/2012, art. 16.2.c), and it always does. The\n  corrective of an invoice that already carried `03` keeps it.\n- `05` (travel agencies) and `07` (cash basis) are accepted, and the invoice PDF carries\n  the mention of their regime (RD 1619/2012, art. 6.1 n and p). `07`: no reverse charge,\n  no non-subject reason and, of the exemptions, only art. 20 or `OTRO`\n  (`REGIME_KEY_CLASSIFICATION_NOT_ACCEPTED`).\n`GET /v1/tax-types` only offers the keys that are accepted.\n\n**One exception to \"a key you send is the key you get\":** when the line ends up\ncarrying an equivalence surcharge — whether you sent `equivalence_surcharge_rate`\nor it was inherited from the company's tax configuration — a `01` is rewritten to\n`18`, because a surcharge under the general regime is fiscally incoherent. Send\n`equivalence_surcharge_rate: 0` explicitly to keep `01`. See\n`equivalence_surcharge_rate` in the invoice line for the full rules.\n","example":"01"},"TaxInfo":{"type":"object","required":["type","percentage"],"properties":{"type":{"$ref":"#/components/schemas/TaxType"},"percentage":{"type":"number","minimum":0,"maximum":100,"description":"Tax percentage","example":21},"regime_key":{"$ref":"#/components/schemas/RegimeKey"}},"description":"Complete tax information with cross-validations:\n- IVA: real rates 4, 10, 21, and the temporary 2, 5 and 7.5 (see below for 0)\n- IGIC: 0, 3, 5, 7, 9.5, 15, 20 — here 0 is the real \"Tipo Cero\"\n- IPSI: real rates 0.5, 1, 2, 4, 8, 10 (see below for 0)\n- OTHER: any percentage between 0 and 100\n\n**0 % under IVA and IPSI is not a rate, it is the exemption sentinel.** It is accepted\non a line, but only together with an `exemption_reason` (exempt or non-subject\noperation); on its own it says nothing and the line is rejected. That is why\n`GET /v1/tax-types` publishes the IVA rates without 0: the legitimate way\nto a 0 % IVA line is through an exemption reason, which the same response also\npublishes. IGIC is different — its 0 % is a real legal rate (basic necessities) and\nneeds no reason.\n\n**IVA 5 %** (the temporary rate applied from 2022 to electricity, gas and certain\nfoodstuffs) is no longer in force for new operations. AEAT only accepts it on operations\ndated from 2022-07-01 to 2024-09-30: send the `operation_date` of that period, because\nwithout one the issue date decides and a line at 5 % is rejected with\n`422 VAT_RATE_NOT_ACCEPTED_ON_DATE`. Its equivalence surcharge pair is 0.5 up to 2022-12-31\nand 0.62 from 2023-01-01. **IVA 2 % and 7.5 %** (temporary rates of the last quarter of 2024)\nare accepted only on operations dated from 2024-10-01 to 2024-12-31, with surcharges 0.26\nand 1.\n\nException: when regime_key = \"17\" (OSS/IOSS) the invoice applies the destination\ncountry VAT instead of the Spanish one, so any percentage in the EU range [0, 27]\nis accepted regardless of the tax type set — including 0 without an exemption reason.\n","example":{"type":"IVA","percentage":21,"regime_key":"01"}},"CustomerValidationUnifiedResult":{"type":"object","required":["metadata","customers_validation","statistics"],"description":"Unified format for customer validation results.\nUsed for bulk validation (JSON), CSV import and Holded Excel import.\n\nOne shape for every bulk and import outcome, so a caller reads a batch creation, a CSV\nimport and a Holded import the same way.\n","properties":{"metadata":{"$ref":"#/components/schemas/CustomerValidationMetadata"},"customers_validation":{"type":"array","items":{"$ref":"#/components/schemas/CustomerValidationItem"},"description":"List of processed customers with their validation status"},"statistics":{"$ref":"#/components/schemas/CustomerValidationStatistics"}}},"CustomerValidationMetadata":{"type":"object","required":["total_customers","is_dry_run","processing_time_ms","source_type"],"description":"Metadata about the validation operation.\nCompatible with bulk JSON, CSV import and Holded Excel import.\n","properties":{"total_customers":{"type":"integer","description":"Total number of customers submitted","example":10},"is_dry_run":{"type":"boolean","description":"Whether it was validation (true) or import (false)","example":true},"processing_time_ms":{"type":"integer","description":"Processing time in milliseconds","example":450},"source_type":{"type":"string","enum":["BULK_JSON","CSV_IMPORT","HOLDED_EXCEL"],"description":"Data source type:\n- BULK_JSON: Data sent as JSON to bulk endpoint\n- CSV_IMPORT: Data parsed from CSV file\n- HOLDED_EXCEL: Data parsed from Holded Excel file\n","example":"CSV_IMPORT"},"filename":{"type":"string","nullable":true,"description":"File name (CSV or Excel depending on source_type)","example":"clientes_enero.csv"},"file_size_bytes":{"type":"integer","nullable":true,"description":"File size in bytes","example":1048576},"total_rows":{"type":"integer","nullable":true,"description":"Total file rows excluding headers","example":250}}},"CustomerValidationItem":{"type":"object","required":["index","status","errors","warnings"],"description":"Individual customer validation result.\nWorks for bulk JSON, CSV import and Holded Excel import.\n","properties":{"index":{"type":"integer","description":"Customer index in the submitted list (0-based for bulk, 1-based for files).\nFor CSV/Excel, represents the row number in the file.\n","example":0},"customer":{"allOf":[{"$ref":"#/components/schemas/CustomerEcho"}],"description":"**Echo of what was submitted**, parsed and validated. Absent when the row could not be\nparsed at all.\n\nIt is deliberately NOT a `Customer`: the same operation also runs as a rehearsal\n(`dry_run=true`), where nothing is written and there is no `id` nor `created_at` to\nreport. Typing it as `Customer` — which requires both — made the contract impossible to\nhonour in one of its two modes, and it was in fact honoured in neither. **The identifier\nof what was created travels in `customer_id`**, at row level, and only when the row\nactually created something.\n"},"customer_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Identifier of the customer this row created. **Present only when the row actually\ncreated something**: absent in a rehearsal (`dry_run=true`), absent on a row that was\nrejected, and absent on a batch that was rejected as a whole.\n\nAfter a `201`, every row carries it, and\n`GET /v1/companies/{company_id}/customers/{customer_id}` answers `200`.\n"},"status":{"type":"string","enum":["VALID","WARNING","ERROR","DUPLICATE","NIF_INVALID"],"description":"Customer validation status:\n- VALID: Completely valid customer\n- WARNING: Valid customer but with non-critical warnings\n- ERROR: Invalid customer, cannot be processed\n- DUPLICATE: Duplicate customer (in batch/CSV or database)\n- NIF_INVALID: the identifier does not match the AEAT census: a NIF the census does not\n  identify, or a type 07 (not registered) whose DNI or NIE the census does identify\n","example":"VALID"},"errors":{"type":"array","description":"List of errors found (block import)","items":{"$ref":"#/components/schemas/CustomerValidationError"}},"warnings":{"type":"array","description":"List of warnings (do not block import)","items":{"$ref":"#/components/schemas/CustomerValidationWarning"}},"row_number":{"type":"integer","nullable":true,"description":"Row number in the file (CSV or Excel depending on source_type)","example":15}}},"CustomerValidationError":{"type":"object","required":["field","value","message","code"],"description":"Field-specific validation error","properties":{"field":{"type":"string","description":"Field that failed validation","example":"nif"},"value":{"type":"string","description":"Value that caused the error","example":"12345678X"},"message":{"type":"string","description":"Human-readable text, already resolved to the language of the request. It is never a raw\ni18n key — a key neither reads nor is stable against a rename.\n","example":"NIF not found in AEAT census"},"code":{"type":"string","description":"Stable, uppercase, never translated — this is what an integration compares. Present\non every row error. When the same failure exists on the single\n`POST /v1/companies/{company_id}/customers`, it is the SAME code, so the single and the\nbulk route never disagree about the same customer (`CLIENT_DUPLICATE`,\n`NIF_NOT_IN_CENSUS`, `NIF_REVOCADO`, `CUSTOMER_IDENTIFIER_REQUIRED`,\n`NIF_INVALID_FORMAT`, …). Failures\nthat only an import has carry their own: `CUSTOMER_DUPLICATED_IN_BATCH`,\n`CUSTOMER_DUPLICATED_IN_FILE`, `CUSTOMER_FIELD_REQUIRED`,\n`CUSTOMER_FIELD_INVALID_FORMAT`, `CUSTOMER_FIELD_INVALID_CHARACTERS`,\n`CUSTOMER_ADDRESS_INVALID`, `CUSTOMER_NOT_SAVED`,\n`CUSTOMER_ROW_NOT_PROCESSED`. An address field of a file row that the JSON contract\nwould have capped carries the address code, blank after trimming or over its length:\n`STREET_EMPTY`, `POSTAL_CODE_EMPTY`, `CITY_EMPTY`, `PROVINCE_EMPTY`,\n`STREET_MAX_LENGTH` (255), `NUMBER_MAX_LENGTH` (20), `CITY_MAX_LENGTH` (100),\n`PROVINCE_MAX_LENGTH` (100). A reason with no specific code yet answers\n`VALIDATION_ERROR`.\n","example":"CUSTOMER_IDENTIFIER_REQUIRED"}}},"CustomerValidationWarning":{"type":"object","required":["field","message"],"description":"Validation warning (does not block import)","properties":{"field":{"type":"string","description":"Field with warning","example":"email"},"message":{"type":"string","description":"Warning message","example":"Email not provided, NIF will be used for invoicing"}}},"CustomerValidationStatistics":{"type":"object","required":["total_processed","valid","with_warnings","with_errors","duplicates","invalid_nifs","imported","success_rate"],"description":"Aggregated validation statistics.\nCompatible with all source types (bulk JSON, CSV and Holded Excel).\n\nEvery field except `imported` counts rows of the submitted file/payload, classified by\noutcome: each row falls into exactly one of `valid`, `with_warnings`, `with_errors`,\n`duplicates` and `invalid_nifs`, so those five add up to `total_processed`.\n`imported` is the only field that counts writes to the database, so it is the field to\nread to know whether an import worked.\n","properties":{"total_processed":{"type":"integer","description":"Total customers processed","example":10},"valid":{"type":"integer","description":"Completely valid customers (no warnings or errors)","example":6},"with_warnings":{"type":"integer","description":"Valid customers but with warnings","example":2},"with_errors":{"type":"integer","description":"Customers with errors (not importable)","example":2},"duplicates":{"type":"integer","description":"Customers with duplicate NIFs","example":1},"invalid_nifs":{"type":"integer","description":"Customers with NIFs not valid in AEAT","example":1},"imported":{"type":"integer","description":"Customers actually created in the database by this request.\nAlways `0` when `is_dry_run=true` (a preview writes nothing). On a real import it is\nthe number of rows that reached the database — normally `importable`, and lower when a\nrow was rejected on write (it is then reported under `duplicates` or `with_errors`).\n","example":8},"success_rate":{"type":"number","format":"float","minimum":0,"maximum":1,"description":"Success rate (valid + with_warnings) / total_processed","example":0.8},"importable":{"type":"integer","description":"Total customers that can be imported (valid + with_warnings)","example":8},"not_importable":{"type":"integer","description":"Total customers that CANNOT be imported (with_errors + duplicates + invalid_nifs)","example":2}}},"RecurringInvoiceResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Billing cadence of the template: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. The day it falls on is\n`day_of_month`, the same anchor for all three. Set it when you create the template;\na template created without it is `MONTHLY`.\n\nThe cadence governs the step from the first invoice onwards, not where that first one\nlands: the first occurrence is the first `day_of_month` on or after `start_date`, found\none month at a time. A yearly template starting 15 February with `day_of_month` 10 first\ninvoices on 10 March, then every 10 March after that.\n"},"day_of_month":{"type":"integer","description":"Day of the month the invoices are issued, as it was requested: this is the anchor of\nthe schedule, not the day the last invoice happened to land on. A month that does not\nhave that day falls back to its last day, so `next_generation` can show a different\nday than this one — a template on `31` generates on 28 February (29 in a leap year)\nand on 30 April.\n\nThe adjustment never sticks and the schedule does not drift: every generation is\nrecalculated from this value, never from the date it was adjusted to\n(31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date"},"end_date":{"type":"string","format":"date","nullable":true},"next_generation":{"type":"string","format":"date","nullable":true,"description":"Date the next invoice is generated — the one this template is actually going to honour.\n`null`, always present and never omitted, when there is none.\n\nThere is none in two cases, and only in those two:\n\n- the template is `COMPLETED`: the date left on it is the one that ran past its end and\n  triggered the closure, not a generation still to come;\n- the template is `PAUSED` and its date is strictly in the past: resuming does not go\n  back to it — it reschedules from today onwards, so that date will never be invoiced.\n\nAn `ACTIVE` template always carries its date, **including a date already in the past**:\nthat is the occurrence about to be generated, and it is your only notice that\nan invoice is on its way.\n\nA `PAUSED` template whose date is still ahead — today counts as ahead — carries it too:\nit is exactly the date resuming would keep.\n\nThe stored schedule is never rewritten by any of this. `next_generation` is what gets\npublished, not what gets kept: resuming a long-paused template still reschedules from\nits own anchor, as it always did.\n"},"draft_in_advance":{"type":"boolean","description":"Whether this template prepares a draft for review before emitting. The review window is\nfixed at 5 days: `true` creates the draft 5 days ahead, `false` goes straight to the\nemission. Either way the invoice is emitted on the scheduled `day_of_month` — what\nchanges is only whether there is a draft to look at first. Generating manually\n(`POST .../generate`) a period whose scheduled date is today or earlier emits the\ninvoice on the spot: its review window has already passed.\n","example":true},"status":{"allOf":[{"$ref":"#/components/schemas/RecurringInvoiceStatus"}]},"is_failing":{"type":"boolean","description":"Whether this template is failing right now — from the FIRST failed unattended run, not\nthe third. The warning email only goes out on every 3rd consecutive retryable failure,\nso `next_generation` not moving used to be the only clue before that; this field\nsurfaces the same signal from the very first one.\n\n`true` when either an unattended run has failed at least once and the retry streak has\nnot been cleared by a successful generation yet (even while `status` is still `ACTIVE`,\nbefore any email fires), or `status` is `PAUSED` with `pause.reason =\nGENERATION_FAILURE`. Derived on every read from data already stored — it is not a new\ncolumn and nothing else changes because of it.\n","example":false},"pause":{"$ref":"#/components/schemas/RecurringInvoicePause"},"completion":{"$ref":"#/components/schemas/RecurringInvoiceCompletion"},"series_id":{"type":"string","format":"uuid"},"series_code":{"type":"string"},"invoice_type":{"$ref":"#/components/schemas/RecurringInvoiceType"},"customer_id":{"type":"string","format":"uuid","nullable":true},"recipient_fiscal_name":{"type":"string","nullable":true},"recipient_nif":{"type":"string","nullable":true,"description":"Spanish Tax ID of the recipient. Mutually exclusive with `recipient_alternative_id`:\na recipient is identified by exactly one of the two, never both, and `null` when the\nother one carries the identifier (or when there is no recipient identified at all).\n"},"recipient_alternative_id":{"allOf":[{"$ref":"#/components/schemas/AlternativeIdentifier"},{"description":"Alternative identifier of the recipient (NIF-IVA, passport, etc.), same shape as\nthe customer's own `alternative_id`. Mutually exclusive with `recipient_nif`:\n`null` when the recipient carries a Spanish `recipient_nif` instead, or when there\nis no recipient identified at all.\n"}]},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLineTemplateResponse"}},"amount":{"type":"number","nullable":true,"example":106,"description":"What the customer will be asked to transfer on the next invoice this template\ngenerates: taxable base + VAT + equivalence surcharge − IRPF withholding +\ndisbursements (`suplidos`). It is NOT the taxable base and NOT the total with VAT:\nthe withholding is already subtracted.\n\nThe same number the next occurrence publishes as `total_to_pay`, to the cent: both\ncome from the template's own lines through the same line pipeline the real generation\nuses. It is computed on the fly and stored nowhere, so it always reflects the lines\nthe template carries right now — and it is not sortable or filterable for that reason.\n\n`null` when the template's lines cannot be priced (no lines, or lines the engine\nrejects). The rest of the template is still returned.\n"},"payment_method":{"type":"string","nullable":true},"payment_iban":{"type":"string","nullable":true,"description":"Full IBAN of the template's own payment method, mirrored from what was sent when it\nwas set. `null` when the template has no payment method.\n"},"payment_swift":{"type":"string","nullable":true,"description":"SWIFT/BIC code of the template's own payment method. `null` when the template has no\npayment method, or when it has one without a SWIFT code.\n"},"payment_term_days":{"type":"integer","nullable":true,"description":"Payment term, in days, of the template's own payment method. `null` when the template\nhas no payment method, or when it has one without a declared term.\n\nEach generated invoice is due on its issue date plus this term. When it is `null`, the\ncompany's default `payment_term_days` (tax configuration) applies, or 30 days if the\ncompany has none.\n"},"notes":{"type":"string","nullable":true},"send_automatically":{"type":"boolean"},"email_configuration":{"$ref":"#/components/schemas/RecurringEmailConfigResponse"},"generated_invoices":{"type":"integer"},"max_invoices":{"type":"integer","nullable":true,"example":6,"description":"Total number of invoices agreed for this template, or absent when the recurrence is not\ncapped by number. It is the TOTAL agreed, not what is left: \"remaining\" is\n`max_invoices - generated_invoices`, a derived reading with no field of its own — two\ncounters of the same fact drift, and the day they do nobody knows which one is true.\n\nWhat counts against it are invoices GENERATED, not calendar turns: a skip does not spend\ncap because it produces no invoice, and a `generate now` does because it produces a real\nfiscal invoice. When the last one is generated the template is closed in the act, with\n`completion.reason = MAX_INVOICES_REACHED`.\n"},"last_generated_at":{"type":"string","format":"date-time","nullable":true},"last_generated_scheduled_date":{"type":"string","format":"date","nullable":true,"example":"2026-05-01","description":"Calendar date of the latest period this template has already invoiced: the highest\n`scheduled_date` in its generation history. `null` when it has never generated an invoice.\n\nIt is NOT the date of `last_generated_at`. That field is the instant the most recent\ninvoice was created; this one is the calendar slot that invoice (or an earlier one) was\nissued for. They diverge on purpose: a `generate now` creates today an invoice for a future\nslot, and a re-anchored schedule can generate a slot out of order, so the latest invoice\ncreated is not necessarily the one with the latest slot.\n\nSkipping a period does not move it: a skip produces no invoice, so it consumes no slot.\n\nDeleting a generated invoice does not move it either, with one exception: deleting the\ndraft of the latest generation releases that slot (the period becomes available to generate\nagain, and `generated_invoices` goes down by one), so this date moves back to the previous\ngenerated slot, or to `null` if there is none.\n"},"source_invoice_id":{"type":"string","format":"uuid","nullable":true},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"InvoiceLineTemplateResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"order":{"type":"integer"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string","nullable":true},"unit_price":{"type":"number","description":"Unit price of the template line. In both total-declared modes\n(`pricing_mode` other than `UNIT_PRICE`) this value is **derived and\ninformational** (`total / quantity`, 4 decimals): re-multiplying it does not\nreproduce the declared total on large quantities.\n"},"pricing_mode":{"$ref":"#/components/schemas/PricingMode"},"total_excluding_tax":{"type":"number","example":1,"description":"Declared line total excluding taxes. Only present on lines with\n`pricing_mode = TOTAL_EXCLUDING_TAX`. It never includes taxes nor subtracts\nIRPF withholding.\n"},"total_including_tax":{"type":"number","example":100,"description":"Declared line total including taxes (taxable base + VAT + equivalence\nsurcharge; IRPF withholding is never subtracted). Only present on lines with\n`pricing_mode = TOTAL_INCLUDING_TAX`. Every invoice this template generates\nreproduces it exactly.\n"},"discount_percentage":{"type":"number","nullable":true},"tax_type":{"type":"string"},"vat_rate":{"type":"number"},"regime_key":{"type":"string"},"equivalence_surcharge_rate":{"type":"number","nullable":true},"irpf_rate":{"type":"number","nullable":true},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text. Only used when exemption_reason is OTRO."}}},"RecurringEmailConfigResponse":{"type":"object","nullable":true,"properties":{"recipients":{"type":"array","items":{"type":"string"}},"cc":{"type":"array","items":{"type":"string"}},"subject":{"type":"string","nullable":true},"message":{"type":"string","nullable":true}}},"UpdateRecurringInvoiceRequest":{"type":"object","description":"Request to **replace** a recurring invoice (`PUT`).\n\nSend the template complete: `end_date`, `payment_method` (with its `payment_iban`,\n`payment_swift` and `payment_term_days`), `notes` and `email_configuration` are\ncleared when you leave them out. To change a few fields and keep the rest\nuntouched, use `PATCH /v1/recurring-invoices/{recurring_invoice_id}`.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S.*$"},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, the current one is kept — it is\nnever reset to `MONTHLY`.\n\n**Changing it reschedules the template.** The next generation is recalculated on the new\ngrid — the `day_of_month` dates anchored at `start_date`, one every cadence — so it can\nland months later than the one you saw before the call. **If the template has already\nissued invoices, the landing respects that**: it is never a period that was already\nbilled, and never before today either — the first point of the new grid strictly after\nthe last invoiced one. It also **discards a pending skip**: the skipped period only\nexisted as a point of the old grid, and that grid is gone. Send the same cadence and\nnothing is rescheduled.\n"},"day_of_month":{"type":"integer","minimum":1,"maximum":31,"description":"Day of the month the invoices are issued. A month that does not have that day falls\nback to its last day: a template on `31` issues on 28 February (29 in a leap year)\nand on 30 April. So `31` is how you ask for the last day of the month it generates\nin — there is no separate flag for it, and the accepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date","description":"Date the subscription started. A past date is accepted and stored as sent — useful\nwhen migrating subscriptions from another system — but it never anchors generation\nin the past. `next_generation` becomes the first date of the template's own calendar\non or after today (today included): the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency`. On a quarterly or yearly template that can be\nmonths from now, not this month. Invoices are never back-dated, so the missed periods\nare not generated. If today's period was already invoiced in the system you are\nmigrating from, send a `start_date` of tomorrow (or of the next period) so it is not\ninvoiced twice.\n\nOnce the template has issued, only a *different* date is rejected with a 422\n`RECURRING_START_DATE_NOT_EDITABLE`. Resending the value it already has is a no-op,\nso this request can keep sending the template complete, as it asks above.\n"},"draft_in_advance":{"type":"boolean","description":"Whether the template prepares a draft for review before emitting. The window is fixed at\n5 days and both options emit on the scheduled day. Generating manually a period whose\nscheduled date is today or earlier emits it on the spot. Omitted, the current value is\nkept.\n"},"preview_days":{"type":"integer","minimum":0,"maximum":30,"deprecated":true,"description":"**Deprecated.** Superseded by `draft_in_advance`, because the review window is no longer\na number you pick: it is fixed at 5 days. Still accepted so nothing breaks — any value\ngreater than `0` means the same as `draft_in_advance: true`, and `0` the same as `false`.\nWhen both are sent, `draft_in_advance` wins. It will be removed in a future version.\n"},"end_date":{"type":"string","format":"date","nullable":true},"max_invoices":{"type":"integer","nullable":true,"example":6,"description":"Total number of invoices this template will generate before ending on its own, between\n2 and 600. Like `end_date`, it is **cleared when you leave it out**: this is a replacement.\n\nA recurrence ends in ONE way: open-ended, on `end_date`, or after `max_invoices`. Sending\nboth with a value is rejected with `RECURRING_END_MODE_CONFLICT`. The range is not enforced\nby this schema on purpose, so the rejection carries its own code and its own message: below\n2 you do not want a recurrence but a scheduled invoice (`PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`).\n\nLowering it below the invoices already generated is rejected: issued invoices are fiscal\nfacts and are not undone by moving the cap. Setting it exactly to the number already\ngenerated is accepted and ends the template in that same call.\n"},"series_id":{"type":"string","format":"uuid"},"invoice_type":{"allOf":[{"$ref":"#/components/schemas/RecurringInvoiceType"}],"description":"Type of the invoices the template generates. Omitted, the current type is kept.\nA change is judged on the resulting template, with the rules of the new type: its series\nmust accept it (`SERIES_INCOMPATIBLE_DOC_TYPE` otherwise), a `SIMPLIFIED` template\ncannot keep an identified recipient (`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`)\nnor lines a simplified invoice does not admit. Send the new `series_id`, and\n`customer_id: null` when moving to `SIMPLIFIED`, in the same call.\n"},"customer_id":{"type":"string","format":"uuid","nullable":true,"description":"Recipient of the generated invoices. Send `null` to leave the template without\na recipient; omit it to keep the current one.\n"},"lines":{"type":"array","minItems":1,"nullable":true,"items":{"$ref":"#/components/schemas/RecurringLineRequest"},"description":"Template lines, replaced as a whole. Omit them (or send `null`) to keep the\ncurrent ones — a template with no lines invoices nothing, so an empty array is\nrejected.\n"},"payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"nullable":true,"description":"Payment method. `payment_iban`, `payment_swift` and `payment_term_days` are\ndetail of this method, not standalone fields: sending any of them without a\n`payment_method` that is present, non-null and different from `NONE` is\nrejected with `422` (`PAYMENT_DETAILS_REQUIRE_METHOD`).\n"},"payment_iban":{"type":"string","nullable":true},"payment_swift":{"type":"string","nullable":true},"payment_term_days":{"type":"integer","nullable":true},"notes":{"type":"string","nullable":true},"send_automatically":{"type":"boolean"},"email_configuration":{"$ref":"#/components/schemas/RecurringEmailConfigRequest"}}},"SetRecurringInvoiceStatusRequest":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"Target status. `PAUSED` stops automatic generation keeping the schedule; `ACTIVE`\nresumes it and keeps the scheduled next generation date whenever that date has not fallen\ndue yet (including today), rescheduling only a date left in the past to the first\noccurrence after today; `COMPLETED` ends the schedule for good — valid from both `ACTIVE`\nand `PAUSED`.\n\n`COMPLETED` is terminal: once there, `ACTIVE` and `PAUSED` are rejected with\n`RECURRING_STATE_TRANSITION_INVALID`. Ending also disarms the auto-emission timer of any\ndraft this schedule had seeded; those drafts stay alive as regular drafts.\n","enum":["ACTIVE","PAUSED","COMPLETED"],"example":"PAUSED"}}},"CreateRecurringInvoiceRequest":{"type":"object","required":["name","day_of_month","start_date","series_id","invoice_type","lines"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S.*$"},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, `MONTHLY` applies.\n\nIt governs the step from the first invoice onwards, not where that first one lands: the\nfirst occurrence is the first `day_of_month` on or after `start_date`, found one month at\na time whatever the cadence. A yearly template starting 15 February with `day_of_month`\n10 first invoices on 10 March, then every 10 March after that — it does not wait a year.\n"},"day_of_month":{"type":"integer","minimum":1,"maximum":31,"description":"Day of the month the invoices are issued. A month that does not have that day falls\nback to its last day: a template on `31` issues on 28 February (29 in a leap year)\nand on 30 April. So `31` is how you ask for the last day of the month it generates\nin — there is no separate flag for it, and the accepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date","description":"Date the subscription started. A past date is accepted and stored as sent — useful\nwhen migrating subscriptions from another system — but it never anchors generation\nin the past. `next_generation` becomes the first date of the template's own calendar\non or after today (today included): the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency`. On a quarterly or yearly template that can be\nmonths from now, not this month. Invoices are never back-dated, so the missed periods\nare not generated. If today's period was already invoiced in the system you are\nmigrating from, send a `start_date` of tomorrow (or of the next period) so it is not\ninvoiced twice.\n"},"draft_in_advance":{"type":"boolean","description":"Whether the template prepares a draft for review before emitting. The window is fixed at\n5 days and both options emit on the scheduled day. Omitted, the template is created\nwithout the review draft.\n"},"preview_days":{"type":"integer","minimum":0,"maximum":30,"deprecated":true,"description":"**Deprecated.** Superseded by `draft_in_advance`, because the review window is no longer\na number you pick: it is fixed at 5 days. Still accepted so nothing breaks — any value\ngreater than `0` means the same as `draft_in_advance: true`, and `0` the same as `false`.\nWhen both are sent, `draft_in_advance` wins. It will be removed in a future version.\n"},"end_date":{"type":"string","format":"date","nullable":true},"series_id":{"type":"string","format":"uuid"},"invoice_type":{"$ref":"#/components/schemas/RecurringInvoiceType"},"max_invoices":{"type":"integer","nullable":true,"example":6,"description":"Total number of invoices this template will generate before ending on its own, between\n2 and 600. Leave it out (or send `null`) for a recurrence that is not capped by number.\n\nA recurrence ends in ONE way: open-ended, on `end_date`, or after `max_invoices`. Sending\nboth `end_date` and `max_invoices` with a value is rejected with `RECURRING_END_MODE_CONFLICT`,\nand so is a `max_invoices` that lands on a template that already has an `end_date`: the\nconflict is judged on the RESULTING state, not on the body. To switch modes, say both things\nin the same call — the new field with a value and the old one as `null`. Nothing is cleared\nsilently.\n\nThe cap counts invoices GENERATED, not calendar turns: a skip does not spend it. The range\nis not enforced by this schema on purpose, so the rejection carries its own code and its own\nmessage: below 2 you do not want a recurrence but a scheduled invoice\n(`PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule`), which also issues it on an exact date.\n"},"customer_id":{"type":"string","format":"uuid","nullable":true},"lines":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/RecurringLineRequest"}},"payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"nullable":true,"description":"Payment method. `payment_iban`, `payment_swift` and `payment_term_days` are\ndetail of this method, not standalone fields: sending any of them without a\n`payment_method` that is present, non-null and different from `NONE` is\nrejected with `422` (`PAYMENT_DETAILS_REQUIRE_METHOD`).\n"},"payment_iban":{"type":"string","nullable":true},"payment_swift":{"type":"string","nullable":true},"payment_term_days":{"type":"integer","nullable":true},"notes":{"type":"string","nullable":true},"send_automatically":{"type":"boolean","default":false},"email_configuration":{"$ref":"#/components/schemas/RecurringEmailConfigRequest"}}},"CreateRecurringFromInvoiceRequest":{"type":"object","required":["name","day_of_month","start_date"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S.*$"},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, `MONTHLY` applies.\n\nIt governs the step from the first invoice onwards, not where that first one lands: the\nfirst occurrence is the first `day_of_month` on or after `start_date`, found one month at\na time whatever the cadence. A yearly template starting 15 February with `day_of_month`\n10 first invoices on 10 March, then every 10 March after that — it does not wait a year.\n"},"day_of_month":{"type":"integer","minimum":1,"maximum":31,"description":"Day of the month the invoices are issued. A month that does not have that day falls\nback to its last day: a template on `31` issues on 28 February (29 in a leap year)\nand on 30 April. So `31` is how you ask for the last day of the month it generates\nin — there is no separate flag for it, and the accepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date","description":"Date the subscription started. A past date is accepted and stored as sent — useful\nwhen migrating subscriptions from another system — but it never anchors generation\nin the past. `next_generation` becomes the first date of the template's own calendar\non or after today (today included): the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency`. On a quarterly or yearly template that can be\nmonths from now, not this month. Invoices are never back-dated, so the missed periods\nare not generated. If today's period was already invoiced in the system you are\nmigrating from, send a `start_date` of tomorrow (or of the next period) so it is not\ninvoiced twice.\n"},"end_date":{"type":"string","format":"date","nullable":true},"send_automatically":{"type":"boolean","default":false},"email_configuration":{"$ref":"#/components/schemas/RecurringEmailConfigRequest"}}},"RecurringLineRequest":{"type":"object","description":"Recurring-invoice line. Unlike invoice lines (which nest tax data under a `main_tax` object),\nrecurring lines use flat tax fields: `vat_rate`, `tax_type`, `regime_key`,\n`equivalence_surcharge_rate` and `irpf_rate`. Do not send a `main_tax` object here.\n\n**Line amount**: send **exactly one** of `unit_price`, `total_excluding_tax` or\n`total_including_tax` — same contract as an invoice line. Sending none, or more than\none, is rejected with `LINE_UNIT_PRICE_XOR_DECLARED_TOTAL`; a declared total together\nwith an explicit `discount_percentage` is rejected with\n`LINE_DECLARED_TOTAL_FORBIDS_DISCOUNT` (the discount, if any, already lives inside\nthe total).\n\nThe mode is what the template promises, and it is re-applied on **every** generation:\na line declared as `total_including_tax: 1.00` over 300 units invoices 1,00 € on every\ngeneration, not 300 × 0,0033 = 0,99 €. Because `PATCH` replaces the lines as a whole,\nresending a line with a different amount field switches its mode.\n","required":["description","quantity","vat_rate"],"properties":{"description":{"type":"string","maxLength":2000},"quantity":{"type":"number","minimum":0.01,"multipleOf":0.0001},"unit":{"type":"string","maxLength":20},"unit_price":{"type":"number","minimum":0,"maximum":999999.9999,"example":50,"description":"Unit price before taxes. Supports up to 4 decimal places for micro-pricing\n(e.g. €0.0897/unit for labels, packaging); the generated invoices always round\ntheir amounts to 2 decimals. Mutually exclusive with `total_excluding_tax` and\n`total_including_tax`.\n\nThe upper bound is the same one the invoice line declares, and so is the\nacceptance of `0` (a discount granted before or simultaneously with the sale,\ne.g. a free introductory month). A price outside the range is rejected with\n`422 VALIDATION_ERROR` and the offending field in `details`.\n\nNegative prices are NOT accepted, unlike an invoice line of a corrective invoice:\na recurring template only issues `STANDARD` or `SIMPLIFIED` invoices, and a\ncorrective is created through its own endpoint over an already issued invoice —\nnever generated by a template.\n"},"total_excluding_tax":{"type":"number","minimum":0,"maximum":99999999.99,"example":1,"description":"Declared line total excluding taxes: this amount IS the taxable base, exactly,\nwith no recalculation. The stored `unit_price` becomes derived and informational\n(`total / quantity`, 4 decimals). Mutually exclusive with `unit_price` and\n`total_including_tax`.\n\nNegative totals are NOT accepted, for the same reason `unit_price` does not accept\nnegative prices: a template only issues `STANDARD` or `SIMPLIFIED` invoices, and only\na corrective invoice — created through its own endpoint over an already issued\ninvoice — carries a negative amount. A total outside the range is rejected with\n`422 VALIDATION_ERROR` and the offending field in `details`.\n\nThe upper bound is the same one `POST /v1/invoices` declares for a line total: a\ntemplate is a promise to issue an invoice, and it cannot accept less than the\ninvoice it will generate does. The derived `unit_price` (`total / quantity`) can\nstill exceed the `maximum` this contract accepts for `unit_price` itself — what\nactually bounds it then is the domain's price ceiling, not this field's contract.\n"},"total_including_tax":{"type":"number","minimum":0,"maximum":99999999.99,"example":100,"description":"Declared line total including taxes — what the customer pays. The engine works\nthe breakdown backwards so that taxable base + VAT + equivalence surcharge equals\nthis amount exactly on every generated invoice. IRPF withholding is never part of\nthe decomposition: it is a retention, not a price. Mutually exclusive with\n`unit_price` and `total_excluding_tax`.\n\nNegative totals are NOT accepted, same as `total_excluding_tax`.\n\nThe upper bound is the same one `POST /v1/invoices` declares for a line total, for\nthe same reason: the template cannot promise more than the invoice it generates\ncould ever accept. The derived `unit_price` (`total / quantity`) can still exceed\nthe `maximum` this contract accepts for `unit_price` itself — what actually bounds\nit then is the domain's price ceiling, not this field's contract.\n"},"discount_percentage":{"type":"number","minimum":0,"maximum":100},"tax_type":{"type":"string","description":"Tax type. Omitted, `IVA` applies."},"vat_rate":{"type":"number"},"regime_key":{"type":"string","description":"VeriFactu regime key. Omitted, `01` (general regime) applies."},"equivalence_surcharge_rate":{"type":"number","nullable":true},"irpf_rate":{"type":"number","nullable":true},"exemption_reason":{"$ref":"#/components/schemas/ExemptionReason","nullable":true},"exemption_reason_text":{"type":"string","nullable":true,"maxLength":500,"description":"Custom exemption text. Only used when `exemption_reason` is `OTRO`.\n\nSame shape as an invoice line: the template declares WHY the operation carries no tax,\nand every invoice it generates inherits it. A 0% line without a reason is rejected on\nwrite — VeriFactu does not accept an exempt line with no explicit motive.\n"}}},"RecurringEmailConfigRequest":{"type":"object","nullable":true,"properties":{"recipients":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"cc":{"type":"array","items":{"$ref":"#/components/schemas/Email"}},"subject":{"type":"string","nullable":true},"message":{"type":"string","nullable":true}}},"GenerationHistoryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"$ref":"#/components/schemas/RecurringInvoiceHistoryEntryType"},"invoice_id":{"type":"string","format":"uuid","nullable":true,"description":"Invoice this entry generated, `null` for entries that generated none — `FAILED`, `SKIPPED`\nand `PAUSED` never produce one. It is also `null` when the invoice is no longer reachable,\nunder the same conditions as `invoice_number`.\n"},"origin":{"allOf":[{"$ref":"#/components/schemas/RecurringInvoiceHistoryEntryOrigin"}],"nullable":true,"description":"Who triggered the generation: `MANUAL` a person did, `UNATTENDED` the nightly sweep. Absent\nfor entries that are not a generation, and for generations recorded before this was stored.\n"},"reason":{"type":"string","nullable":true,"description":"Human-readable explanation of a `FAILED` or `PAUSED` entry, already translated to the\ncaller's language. `null` when the entry carries no reason (a generation or a skip).\n\nIt is presentation text, not a stable code: do not branch on it. The stable fact is `type`.\n"},"requested_by":{"type":"string","format":"uuid","nullable":true,"description":"User who asked to skip the slot. Only present for `SKIPPED` entries.\n"},"requested_by_name":{"type":"string","nullable":true,"description":"Human-readable identity of `requested_by`: the user's name when the backend has one,\ntheir email otherwise. Never the raw UUID — that stays in `requested_by`. Only present\nfor `SKIPPED` entries; `null` when there is no `requested_by` (a generation, failure or\npause).\n"},"generated_at":{"type":"string","format":"date-time"},"scheduled_date":{"type":"string","format":"date"},"invoice_number":{"type":"string","nullable":true,"description":"Number of the invoice this entry generated, `null` when that invoice is no longer\nreachable — it was deleted while still a draft, or it falls outside the caller's scope.\nA draft that has not been issued yet has no number either, and reads `null` too.\n\nThe entry itself always survives: an occurrence that happened is not unsaid by the\ninvoice it produced disappearing.\n"},"total":{"type":"number","nullable":true,"description":"Total amount of the generated invoice, `null` under the same conditions as\n`invoice_number`. Resolved on read, so it always reflects the invoice as it is now.\n"},"status":{"allOf":[{"$ref":"#/components/schemas/InvoiceStatus"}],"nullable":true,"description":"Current status of the generated invoice, `null` under the same conditions as\n`invoice_number`. Resolved on read and never stored on the history row: an entry\ngenerated months ago reports what its invoice is TODAY, not what it was when born.\n"}}},"RecurringInvoiceType":{"type":"string","enum":["STANDARD","SIMPLIFIED"],"description":"Type of the invoices a recurring template generates: `STANDARD` for an identified recipient,\n`SIMPLIFIED` for a recipient that is not identified.\n"},"CreateCompanyRequest":{"type":"object","required":["nif","legal_name","entity_type","address"],"properties":{"nif":{"type":"string","description":"NIF/CIF of the business","example":"B12345674"},"legal_name":{"type":"string","description":"Legal/fiscal name","example":"Mi Empresa SL"},"entity_type":{"$ref":"#/components/schemas/EntityType"},"address":{"$ref":"#/components/schemas/Address"},"legal_form":{"type":"string","description":"Legal form (SL, SA, ...). Recommended for LEGAL_ENTITY.","example":"SL"},"legal_representative":{"$ref":"#/components/schemas/LegalRepresentative"},"trade_name":{"type":"string","description":"Commercial/trade name (optional). When omitted or blank, the company is read back with `trade_name` equal to `legal_name`: the field always carries the name to display.","example":"Mi Empresa"},"default_main_tax":{"$ref":"#/components/schemas/TaxInfo"},"default_irpf_rate":{"type":"number","minimum":0,"maximum":100,"description":"Default IRPF retention rate for this company's invoices. Omit it and the company is created with no withholding — BeeL never assumes a rate nobody declared.","example":15},"numbering":{"$ref":"#/components/schemas/CompanyNumbering"},"aeat_environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"default":"TEST","x-field-extra-annotation":"@com.fasterxml.jackson.annotation.JsonAlias(\"environment\")","description":"AEAT/VeriFactu environment to register this NIF against.\n* `TEST` — Sandbox NIF, invoices reach VeriFactu test (default).\n* `PROD` — Production NIF; requires the AEAT representation\n  model to be signed (`POST /v1/companies/{company_id}/representation/submit`)\n  before real invoices can be issued.\n\nThis field was previously named `environment`. The old name is still accepted as an\nalias for backwards compatibility and will be withdrawn in a future major version —\nsend `aeat_environment`.\n"},"activate":{"type":"boolean","default":true,"description":"Whether to **switch the company on** in `aeat_environment` as part of this call.\n\nCreating a company and activating it are two different acts. The company record is free\nand always creatable; the activation is what seeds the invoice series, registers the\nNIF and — in `PROD` — is what gets billed.\n\n* `true` (default) — unchanged behaviour: the company is created and switched on in\n  `aeat_environment`, with its default series seeded there.\n* `false` — only the company record is created. It is switched on nowhere, has\n  no series and cannot issue yet; `aeat_environment` is ignored. Activate it later\n  with `POST /v1/companies/{company_id}/activations`, which is also\n  the only door that opens a Stripe Checkout when the account has no card on file.\n\nSeries numbering travels with the activation that seeds it: a request with\n`activate: false` and a `numbering` block that asks for anything is rejected with\n`422` `NUMBERING_REQUIRES_ACTIVATION` — the later activation door does not accept\nnumbering, so silently accepting it here would discard it forever. Either drop the\n`numbering` block or activate a mode in the same call.\n"}}},"UpdateCompanyRequest":{"type":"object","description":"Editable fields of a company.\n`entity_type`, `nif` and `legal_form` are immutable once set;\n`legal_name` can only be corrected together with a successful\nAEAT census re-validation of the NIF. For a company that\nre-validation checks the CIF only — the name is **not verified**,\nso it can never fail because of the name you send.\n\n**Everything here reads back.** The `200` of the update — and every later\n`GET /v1/companies/{company_id}` — returns `CompanyData`, which carries each of these\nfields under the same name and the same type. No need to keep your own copy to\nreconcile: write it, read it back. Five of them also have a read of their own,\n`logo_url`, `invoice_template_type`, `invoice_accent_color`, `invoice_language` and\n`email_language`, all returned by\n`GET /v1/companies/{company_id}/invoice-customization`.\n\nA field you never set is **absent** from the read, which means \"nothing stored\" — not\n\"hidden from you\", and not a default. What varies by caller is what you may *write*\n(see the Test-credential rule below), never what comes back.\n\n**From Test, on a company activated in Live, only six of these fields are writable**:\n`logo_url`, `invoice_accent_color`, `invoice_template_type`, `invoice_language`,\n`email_language` and `additional_info` — what the invoice *looks like* and the free\nnote it carries. Everything else describes the real business and answers\n`422 FISCAL_IDENTITY_LIVE_ONLY` unless the call is made with a live credential.\n","properties":{"entity_type":{"allOf":[{"$ref":"#/components/schemas/EntityType"},{"nullable":true,"description":"Entity type. IMMUTABLE once set."}]},"legal_name":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Legal/fiscal name. Changing it triggers AEAT census re-validation of the NIF.\nFor a self-employed individual the census matches NIF and name together, so a\nname it does not recognise is rejected. For a legal entity the name is\n**not verified**: the re-validation only confirms the CIF, and the business\nname held by the census is the only thing to contrast yours against.\n"},"nif":{"allOf":[{"$ref":"#/components/schemas/NIF"},{"nullable":true,"description":"NIF/CIF. IMMUTABLE once set."}]},"legal_form":{"type":"string","minLength":1,"maxLength":100,"nullable":true,"description":"Legal form (SL, SA, ...). IMMUTABLE once set. Only for LEGAL_ENTITY."},"trade_name":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Commercial/trade name for the company"},"address":{"$ref":"#/components/schemas/Address"},"legal_representative":{"$ref":"#/components/schemas/LegalRepresentative"},"phone":{"allOf":[{"$ref":"#/components/schemas/PhoneInput"}],"nullable":true},"email":{"allOf":[{"$ref":"#/components/schemas/Email"},{"nullable":true}]},"website":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Website"},"logo_url":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Logo URL"},"additional_info":{"type":"string","maxLength":500,"nullable":true,"description":"Free note printed on the invoice. It is presentation, not fiscal identity, so a\ntest credential may change it even on a company activated in Live. Read it back in\n`CompanyData.additional_info` (`GET /v1/companies/{company_id}`).\n"},"default_iban":{"allOf":[{"$ref":"#/components/schemas/IBAN"},{"nullable":true}]},"default_swift":{"allOf":[{"$ref":"#/components/schemas/SWIFT"},{"nullable":true}]},"account_holder":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Bank account holder"},"iae":{"type":"string","minLength":1,"maxLength":20,"nullable":true,"description":"IAE code"},"activity_start_date":{"type":"string","format":"date","nullable":true,"description":"Activity start date"},"default_payment_term":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Default payment term in days"},"invoice_template_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceTemplateType"}],"nullable":true,"description":"Invoice PDF template type"},"invoice_accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","nullable":true,"description":"Invoice PDF accent color (#RRGGBB)","example":"#fc481d"},"invoice_language":{"allOf":[{"$ref":"#/components/schemas/Language"},{"nullable":true,"description":"Language for invoice PDFs"}]},"email_language":{"allOf":[{"$ref":"#/components/schemas/Language"},{"nullable":true,"description":"Language for emails"}]}}},"CompanyData":{"type":"object","description":"A company owned by the account.\n\nIts status is described by **two independent axes** — do not conflate them:\n\n* **Mode activation** (`in_test` / `in_prod`) — which modes this NIF is\n  activated in, matching the dashboard's Test/Live switch. A company is a\n  single env-agnostic record — there is no Test copy and no Live copy of it;\n  what is per-mode is the *activation*, and a NIF can be activated in Test, in\n  Live, in both, or in neither. It decides where invoices and quota are\n  accounted, NOT whether they reach AEAT.\n* **AEAT emission capability** (`account_state`, `verifactu_status`, and the\n  `readiness` block requested with `?include=readiness`) — whether invoices are\n  submitted to AEAT with a signed VeriFactu representation.\n\nA company can bill in the **Live** mode and still have AEAT emission disabled\n(e.g. before the VeriFactu representation is signed). `TEST_ONLY` /\n`in_prod=false` therefore do **not** mean \"this account cannot invoice\".\n\n**Every field `PATCH /v1/companies/{company_id}` accepts is read back here** — same\nnames, same types as `UpdateCompanyRequest`. A field with no value is absent, which\nmeans \"nothing stored\", never \"hidden from you\": the field set does not depend on who\nasks (owner, member with `VIEW`, API key or the managed twin\n`GET /v1/accounts/{account_id}/companies` all see the same one).\n","properties":{"id":{"type":"string","format":"uuid","description":"Unique company identifier."},"access_level":{"allOf":[{"$ref":"#/components/schemas/AccessLevel"}],"description":"How much access the signed-in person has to this company, so a dashboard can label it (\"read only\") and hide write actions instead of discovering the answer through a `403`. Read-only and derived from who is asking — never sent.\n**Only present when authenticating with a user session.** With an API key the field is absent: what a key may do is set by its own scopes, which are per resource (`invoices:write`, `customers:read`, …) and cannot be reduced to one of three levels. Absent therefore means \"does not apply here\", never \"no access\"."},"nif":{"type":"string","description":"Spanish Tax ID (NIF/CIF). Immutable once set."},"legal_name":{"type":"string","description":"Legal/fiscal name of the company. Immutable once set.\n\nIt is the name that was sent when the NIF was registered, not one AEAT\nconfirmed: for a legal entity the census identifies the CIF and the name is\n**not verified**. Validate a NIF (`POST /v1/nif/validate`) to read the\nbusiness name the census actually holds.\n"},"trade_name":{"type":"string","description":"Commercial/trade name shown in the UI and on invoices. Editable. Never empty: when no trade name has been set, it carries `legal_name`, so `trade_name == legal_name` means \"no trade name of its own\", not that one was written."},"entity_type":{"allOf":[{"$ref":"#/components/schemas/EntityType"}],"description":"Self-employed individual or legal entity. Immutable once set."},"is_primary":{"type":"boolean","description":"True for the account's primary company (the owner's own NIF)."},"verifactu_status":{"type":"string","enum":["NOT_CONFIGURED","PENDING","ACTIVE","ERROR"],"description":"State of the AEAT representation of this NIF, which Live submission to VeriFactu\nrequires. Part of the AEAT-emission axis — independent of `environment`.\n\n- `NOT_CONFIGURED`: no representation document, or the last one was cancelled.\n- `PENDING`: the document is generated and waits to be signed and submitted.\n- `ACTIVE`: the signed representation is in force.\n- `ERROR`: reserved; not currently returned.\n"},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"deprecated":true,"description":"**Deprecated** — use `in_test` / `in_prod` instead.\n\nA company is a single env-agnostic record; what is per-mode is its\n*activation*, and a company can be activated in Test, in Live, in both, or\nin neither. This single scalar cannot express that: it collapses the pair to\none value, so it misreports any company activated in both modes. It is kept\nfor backwards compatibility and will be removed in a future version.\n\n**How the collapse is decided** — the same rule wherever this field appears\n(the single company, the account's list of companies, any of their aliases):\nthe environment of the credential you are calling with, *if* the company is\nactivated there; otherwise the environment it is really activated in. A\ncompany activated only in Test therefore reads `TEST` even to a live key: the\nscalar is never re-labelled to the environment you are looking from.\n\nThis is the data/billing axis, NOT a measure of AEAT capability.\n"},"in_prod":{"type":"boolean","description":"Whether this company is **activated in Live** (production), regardless\nof whether the AEAT representation is already signed. Derived from the real\nactivation fact — NOT from the stamped `environment` scalar — so it stays true\neven for a NIF registered in production but not yet signed. Drives the UI's\n\"already in production\" state (no spurious \"add NIF\" prompt when the\naccount switches to Live).\n"},"in_test":{"type":"boolean","description":"Whether this company is **activated in Test** (sandbox). Symmetric to\n`in_prod` and derived from the same activation fact: activation is independent\nper mode, so a company may be activated in Test only, in Live only, in both, or\nin neither. A company that is not activated in the mode you are working in is\nshown *switched off* in the dashboard (visible and labelled, but not selectable)\nrather than hidden.\n"},"aeat_environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"deprecated":true,"nullable":true,"description":"**Deprecated** — use the activation pair `in_test` / `in_prod`, together with\n`verifactu_status`, instead.\n\nAEAT/VeriFactu environment this NIF is registered against. It is derived, never\nstored: a single scalar cannot express a NIF activated in both modes, nor one\nactivated in neither. It is `null` whenever no registration can be asserted —\nno mode activated, or `verifactu_status: NOT_CONFIGURED` — and it is kept for\nbackwards compatibility only.\n"},"account_state":{"allOf":[{"$ref":"#/components/schemas/AccountState"}],"description":"Account-level lifecycle, identical for every company of the account. AEAT emission\nadditionally requires the per-NIF representation to be signed.\n"},"address":{"type":"object","nullable":true,"description":"Fiscal address of the company, as set via `POST /v1/accounts/{account_id}/companies` and\n`PATCH /v1/accounts/{account_id}/companies/{company_id}`. All fields are optional — a company\nmay have a partial address — and the object is `null` when no address\nhas been set.\n","properties":{"street":{"type":"string","description":"Street of the fiscal address."},"number":{"type":"string","description":"Street number."},"floor":{"type":"string","description":"Floor or level."},"door":{"type":"string","description":"Door or apartment."},"postal_code":{"type":"string","description":"Postal code."},"city":{"type":"string","description":"City or town."},"province":{"type":"string","description":"Province."},"country":{"type":"string","description":"Country name."},"country_code":{"type":"string","description":"ISO 3166-1 alpha-2 country code."}}},"legal_form":{"type":"string","minLength":1,"maxLength":100,"nullable":true,"description":"Legal form (SL, SA, ...). Only for `entity_type: LEGAL_ENTITY`, immutable once set.\nThe field `UpdateCompanyRequest.legal_form` writes.\n"},"legal_representative":{"allOf":[{"$ref":"#/components/schemas/LegalRepresentativeResponse"}],"nullable":true,"description":"Legal representative of the company, as stored — the field\n`UpdateCompanyRequest.legal_representative` writes. Only for\n`entity_type: LEGAL_ENTITY`; absent when none is recorded.\n"},"phone":{"allOf":[{"$ref":"#/components/schemas/Phone"}],"nullable":true,"description":"Contact phone. The field `UpdateCompanyRequest.phone` writes."},"email":{"allOf":[{"$ref":"#/components/schemas/Email"}],"nullable":true,"description":"Contact email. The field `UpdateCompanyRequest.email` writes."},"website":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Website. The field `UpdateCompanyRequest.website` writes."},"logo_url":{"type":"string","minLength":1,"maxLength":500,"nullable":true,"description":"Public URL of the logo printed on the invoice. The field `UpdateCompanyRequest.logo_url`\nwrites. Also returned by `GET /v1/companies/{company_id}/invoice-customization` and as\n`issuer.logo_url` of an invoice, always as the same absolute, fetchable URL.\n"},"additional_info":{"type":"string","maxLength":500,"nullable":true,"description":"Free note printed on the invoice — the field\n`UpdateCompanyRequest.additional_info` writes.\n"},"default_iban":{"allOf":[{"$ref":"#/components/schemas/IBAN"}],"nullable":true,"description":"Default IBAN shown on the invoice. The field `UpdateCompanyRequest.default_iban`\nwrites.\n"},"default_swift":{"allOf":[{"$ref":"#/components/schemas/SWIFT"}],"nullable":true,"description":"Default SWIFT/BIC. The field `UpdateCompanyRequest.default_swift` writes.\n"},"account_holder":{"type":"string","minLength":1,"maxLength":255,"nullable":true,"description":"Bank account holder. The field `UpdateCompanyRequest.account_holder` writes.\n"},"iae":{"type":"string","minLength":1,"maxLength":20,"nullable":true,"description":"IAE code. The field `UpdateCompanyRequest.iae` writes."},"activity_start_date":{"type":"string","format":"date","nullable":true,"description":"Activity start date. The field `UpdateCompanyRequest.activity_start_date` writes.\n"},"default_payment_term":{"type":"integer","minimum":0,"maximum":365,"nullable":true,"description":"Default payment term in days. The field\n`UpdateCompanyRequest.default_payment_term` writes.\n"},"invoice_template_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceTemplateType"}],"nullable":true,"description":"Invoice PDF template type. The field\n`UpdateCompanyRequest.invoice_template_type` writes. Also returned by\n`GET /v1/companies/{company_id}/invoice-customization`.\n"},"invoice_accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","nullable":true,"description":"Invoice PDF accent color (`#RRGGBB`). The field\n`UpdateCompanyRequest.invoice_accent_color` writes. Also returned by\n`GET /v1/companies/{company_id}/invoice-customization`.\n","example":"#fc481d"},"invoice_language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"nullable":true,"description":"Language used to render invoice PDFs. The field\n`UpdateCompanyRequest.invoice_language` writes. Also returned by\n`GET /v1/companies/{company_id}/invoice-customization`.\n"},"email_language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"nullable":true,"description":"Language used for emails. The field `UpdateCompanyRequest.email_language`\nwrites. Also returned by `GET /v1/companies/{company_id}/invoice-customization`.\n"},"default_irpf_rate":{"type":"number","nullable":true,"description":"Default IRPF withholding rate applied to this company's invoices — the same number\n`POST /v1/accounts/{account_id}/companies` accepts and\n`GET`/`PUT /v1/companies/{company_id}/tax-configuration` manages, read back\nwhere you wrote it.\n\n**Absent means no rate was ever declared**, which is not the same as `0`. A company\ncreated without `default_irpf_rate` withholds nothing *because nobody said what to\nwithhold*; a company with `0` withholds nothing *because its owner declared so*. Both\ninvoice the same, only one is a declaration — so do not fill the absent case in with a\nzero. Exemption is a declaration too and reads as `0`.\n\nRead-only here. Change it with `PUT /v1/companies/{company_id}/tax-configuration`;\nthis resource does not accept it on `PATCH`.\n\nAbsent from the `201` of `POST /v1/accounts/{account_id}/companies` (that response describes the company just\ncreated, not its resolved tax configuration) — read it back with `GET`.\n"},"created_at":{"type":"string","format":"date-time","description":"When the company was created (ISO 8601)."},"readiness":{"allOf":[{"$ref":"#/components/schemas/IssuingReadinessData"}],"nullable":true,"description":"Issuing-readiness status of this company. Present ONLY when the request\nasks for it via `?include=readiness`; otherwise omitted/null. Same shape as\n`GET /v1/companies/{company_id}/issuing-readiness`.\n"}}},"CompanyCreatedData":{"allOf":[{"$ref":"#/components/schemas/CompanyData"},{"type":"object","description":"The company exactly as `GET /v1/companies/{company_id}` returns it — every field of\n`CompanyData`, with the same values — plus `series`. On the env-additive path (the same\nNIF already living in the account) those are the details in force, which may differ\nfrom the ones sent.\n","properties":{"series":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceSeries"},"description":"Invoice series seeded by the activation this request performed, one per\ndocument type (ordinary, simplified, corrective) — the same shape as\n`GET /v1/companies/{company_id}/series`, so no second call is needed to confirm\nwhich series the company was born with (including any custom `numbering`\nthe request carried).\n\nAn empty array when the request did not activate any mode (`activate: false`\nseeds no series). On an env-additive activation (same NIF, other environment)\nit reflects the series of the environment just activated.\n"}}}]},"CompanyResponse201":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CompanyCreatedData"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"CompanyResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CompanyData"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"ListCompanies200Response":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["companies","pagination"],"properties":{"companies":{"type":"array","items":{"$ref":"#/components/schemas/CompanyData"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"RepresentationActionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string"},"message":{"type":"string"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"RepresentationDownloadResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"download_url":{"type":"string","format":"uri"},"expires_in_seconds":{"type":"integer"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"RepresentationStatusResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"status":{"type":"string","enum":["NOT_STARTED","PDF_GENERATED","SUBMITTED","ACTIVE","ERROR","CANCELLED"],"description":"Stage of the AEAT representation of the company.\n\n- `NOT_STARTED`: no representation document has been generated yet.\n- `PDF_GENERATED`: the document is generated and waits for a digital signature.\n- `SUBMITTED`: returned only by the submit operation, as its acknowledgement; this\n  read never returns it.\n- `ACTIVE`: the signed document was accepted and the representation is in force,\n  so invoices of this NIF can be submitted to the AEAT in Live.\n- `ERROR`: reserved; not currently returned.\n- `CANCELLED`: the representation was cancelled. Invoices of this NIF are not\n  submitted to the AEAT in Live until a new signed document is accepted.\n"},"message":{"type":"string"},"updated_at":{"type":"string","format":"date-time"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"CompanyLogo":{"type":"object","required":["logo_url"],"properties":{"logo_url":{"type":"string","format":"uri","description":"Public URL of the stored logo.","example":"https://storage.example.com/beel-public/logos/9a1f.../company-logo.png"}}},"InvoiceCustomization":{"type":"object","required":["company_id","invoice_template_type","invoice_accent_color","invoice_language","email_language"],"properties":{"company_id":{"type":"string","format":"uuid","description":"Unique identifier (UUID) of the company this customization belongs to."},"invoice_template_type":{"$ref":"#/components/schemas/InvoiceTemplateType"},"invoice_accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"Accent colour applied to the invoice PDF, in `#RRGGBB` format.","example":"#fc481d"},"invoice_language":{"$ref":"#/components/schemas/Language"},"email_language":{"$ref":"#/components/schemas/Language"},"logo_url":{"type":"string","format":"uri","nullable":true,"description":"Public URL of the logo, or `null` when none is set."}}},"UpdateInvoiceCustomizationRequest":{"type":"object","description":"Partial update. Properties absent from the body are left unchanged; the logo is managed through the `logo` sub-resource.","properties":{"invoice_template_type":{"allOf":[{"$ref":"#/components/schemas/InvoiceTemplateType"}],"nullable":true,"description":"Template used to render the invoice PDF."},"invoice_accent_color":{"type":"string","pattern":"^#[0-9A-Fa-f]{6}$","description":"Accent colour applied to the invoice PDF, in `#RRGGBB` format.","example":"#fc481d"},"invoice_language":{"allOf":[{"$ref":"#/components/schemas/Language"},{"nullable":true,"description":"Language used to render the invoice PDF."}]},"email_language":{"allOf":[{"$ref":"#/components/schemas/Language"},{"nullable":true,"description":"Language used for the emails that deliver the invoice."}]}}},"InitiatePaymentConnectionRequest":{"type":"object","description":"The authorization to open: which provider it is for, and where to send the holder back.\n","required":["provider"],"properties":{"provider":{"type":"string","description":"Payment provider slug in **lowercase**. Currently only `stripe` (Stripe Connect) is\noperative; `woocommerce` and `shopify` are reserved for future providers. Any other\nvalue answers `422`.\n","example":"stripe"},"return_url":{"type":"string","pattern":"^https://.*","description":"URL of your portal to redirect the account holder back to after the OAuth callback\ncompletes. Must be an absolute `https://` URL. On **success** BeeL appends\n`status=success`, `provider` (slug), `company_id`, `connection_id` and `account`\n(the provider account id, e.g. `acct_...`). On **error** it appends `status=error`,\n`provider` and `message`, always a stable uppercase error code: `OAUTH_STATE_INVALID`\n(the authorization is unknown, expired or already used), `OAUTH_TOKEN_EXCHANGE_FAILED`\n(the provider rejected the code exchange), `ACCESS_DENIED` (the account holder declined\nat the provider), `OAUTH_ACCOUNT_CONNECTED_TO_OTHER_COMPANY` (the provider account is\nalready connected to another NIF; disconnect it there first),\n`PROVIDER_ERROR` (any other provider-reported failure) or\n`OAUTH_UNEXPECTED`. When omitted, the callback redirects to BeeL's default integrations\nscreen. A `return_url` that is not an absolute `https://` URL with a host is rejected\nup front with `422` `PAYMENT_RETURN_URL_INVALID`, and no authorization is opened.\n","example":"https://your-platform.example.com/connections/stripe/return"}}},"InitiatePaymentConnectionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["authorization_url"],"properties":{"authorization_url":{"type":"string","format":"uri","description":"URL where the NIF's holder authorizes the Stripe Connect connection."}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"CompanyPaymentConnection":{"type":"object","description":"A payment provider connection of a NIF with the settings that decide what BeeL does with\neach incoming charge. Credentials of the provider are never part of this document.\n","required":["id","provider","external_account_id","environment","status","auto_invoice_enabled","event_source","auto_create_customer","send_invoice_by_email","active_filters"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the connection, the `{connection_id}` that addresses it.","example":"5f2b1c0e-9a3d-4c11-8f6e-7b0d2a4c9e13"},"provider":{"type":"string","description":"Payment provider slug (lowercase).","example":"stripe"},"external_account_id":{"type":"string","description":"Provider-side account id (e.g. Stripe `acct_...`). Read-only.","example":"acct_1234567890"},"external_account_name":{"type":"string","nullable":true,"description":"Provider-side account display name, if any."},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"description":"Mode the connection lives in (`beel_sk_test_*` → `TEST`, `beel_sk_live_*` → `PROD`).\nRead-only: Test and Live connections are independent and never move between modes.\n"},"status":{"$ref":"#/components/schemas/ConnectionStatus"},"connected_at":{"type":"string","format":"date-time","nullable":true,"description":"When the connection was established. Read-only."},"last_event_at":{"type":"string","format":"date-time","nullable":true,"description":"When the last charge from the provider came in through this connection. Read-only, and\na signal of **traffic**, not of success: it is stamped even when the charge is filtered\nout. `null` means no charge has ever arrived.\n"},"auto_invoice_enabled":{"type":"boolean","description":"Whether incoming charges are auto-invoiced. Disconnecting the connection does not\nchange it.\n"},"event_source":{"allOf":[{"$ref":"#/components/schemas/PaymentConnectionEventSource"}],"description":"Which family of Stripe events this connection acts on. A single webhook endpoint\nreceives everything the connected account emits, so the events outside the declared\nfamily are recorded as skipped and deliberately ignored, and need no attention.\n"},"auto_create_customer":{"type":"boolean","description":"Whether customers are created from the fiscal data the provider supplies."},"send_invoice_by_email":{"type":"boolean","description":"Whether the auto-issued invoice is emailed to the payer, using the address the provider\nreported. A charge with no payer email is invoiced all the same and sends nothing.\n"},"tax_inclusive_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"}],"nullable":true,"description":"The tax applied to a charge for which the provider sends no tax breakdown (for example,\nStripe without Stripe Tax). When set, it takes precedence over the company's tax\nconfiguration for those charges. `null` means the company's tax configuration applies.\n"},"series":{"$ref":"#/components/schemas/CompanyPaymentConnectionSeries"},"simplificada_threshold":{"type":"number","format":"double","deprecated":true,"description":"Deprecated. No longer affects auto-invoicing: a buyer with a tax ID and full address\nalways gets an ordinary invoice; without them, a simplified one up to 3,000 EUR.\nStill accepted and returned.\n","example":400},"filter_config":{"$ref":"#/components/schemas/CompanyPaymentConnectionFilters"},"active_filters":{"type":"array","description":"Filter axes that currently hold a value, so you can tell a connection that filters\nnothing from one that does without reading every field. An empty list means every\ncharge reaches auto-invoicing.\n\n- `amount`: `min_amount` or `max_amount` is set.\n- `customer`: `only_mapped_customers` is on or `allowed_customer_ids` has entries.\n- `description`: `excluded_description_patterns` or `required_description_patterns`\n  has entries.\n- `category`: `disabled_categories` has entries.\n","items":{"type":"string","enum":["amount","customer","description","category"]},"example":["amount","description"]}}},"CompanyPaymentConnectionResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/CompanyPaymentConnection"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"UpdateCompanyPaymentConnectionRequest":{"type":"object","description":"The settings to change. A field you omit keeps its current value; the series fields and\n`tax_inclusive_tax` also accept an explicit `null`, which clears them.\n","properties":{"auto_invoice_enabled":{"type":"boolean","description":"Whether incoming charges are auto-invoiced."},"event_source":{"allOf":[{"$ref":"#/components/schemas/PaymentConnectionEventSource"}],"description":"Which family of Stripe events this connection acts on. Changing it affects the events\nthat arrive from then on; events already recorded keep their outcome.\n"},"auto_create_customer":{"type":"boolean","description":"Whether customers are created from the fiscal data the provider supplies."},"send_invoice_by_email":{"type":"boolean","description":"Whether the auto-issued invoice is emailed to the payer."},"tax_inclusive_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"}],"nullable":true,"description":"Optional. The tax applied to a charge for which the provider sends no tax breakdown;\nwhen set, it takes precedence over the company's tax configuration for those charges.\nOmit it to keep the current value; send `null` to clear it, so the company's tax\nconfiguration applies again.\n"},"series":{"$ref":"#/components/schemas/UpdateCompanyPaymentConnectionSeries"},"simplificada_threshold":{"type":"number","format":"double","minimum":0.01,"maximum":3000,"deprecated":true,"description":"Deprecated. No longer affects auto-invoicing: a buyer with a tax ID and full address\nalways gets an ordinary invoice; without them, a simplified one up to 3,000 EUR.\nStill accepted; between 0.01 and 3000.00.\n"},"filter_config":{"allOf":[{"$ref":"#/components/schemas/CompanyPaymentConnectionFilters"}],"description":"Omit to keep the current filter configuration untouched. When sent, it **replaces the\nwhole object** — every axis you leave out is cleared, not preserved. Send the complete\nconfiguration you want, not just the axis you are changing.\n"}}},"ManagedPaymentEvent":{"type":"object","description":"A payment event of a managed NIF's connection (curated subset).","required":["id","provider","event_type","event_kind","external_event_id","status","needs_action","draft_available","retry_available","discard_available","retry_count","received_at","discarded"],"properties":{"id":{"type":"string","format":"uuid","description":"Identifier of the payment event."},"provider":{"type":"string","description":"Payment provider slug (lowercase).","example":"stripe"},"event_type":{"type":"string","description":"Raw event name as the provider emitted it. Preserved verbatim so you can trace the\nevent back on the provider's side. Not filterable — filter by `event_kind`.\n","example":"payment_intent.succeeded"},"event_kind":{"allOf":[{"$ref":"#/components/schemas/PaymentEventType"}],"description":"What the event is about, once `event_type` has been classified. This is the value the\n`event_kind` filter takes, so what you read here you can ask for again.\n","example":"PAYMENT_COMPLETED"},"external_event_id":{"type":"string","description":"Provider-side event identifier.","example":"evt_1234567890"},"external_payment_id":{"type":"string","nullable":true,"description":"Canonical identity of the payment at the provider. On Stripe this is always the\nPaymentIntent id (`pi_…`), so every event of the same payment carries the same value\nand can be grouped by it. Null while the identity is not yet resolved.\nTo trace the event back to the object that delivered it, use `source_object_id`.\n","example":"pi_1234567890"},"source_object_id":{"type":"string","nullable":true,"description":"Provider object that delivered this event (`in_…`, `cs_…`, `ch_…`, `pi_…`). Use it to\nopen the originating invoice, checkout session or charge at the provider. It is an\naudit trail only: it never identifies the payment — `external_payment_id` does.\n","example":"in_1234567890"},"currency":{"type":"string","nullable":true,"description":"ISO 4217 currency of the event. Absent on an event that moves no money — a failed\nintent, or a provider notice we do not classify — and then `amount` is absent too.\n","example":"EUR"},"amount":{"type":"number","nullable":true,"description":"Amount the event carries. Absent whenever `currency` is: the two travel together, so\nan amount never arrives without the currency that gives it meaning.\n","example":150},"fee_amount":{"type":"number","nullable":true,"description":"Provider fee, when reported."},"net_amount":{"type":"number","nullable":true,"description":"Amount net of the provider fee, when reported."},"category":{"allOf":[{"$ref":"#/components/schemas/PaymentEventCategory"}],"nullable":true,"description":"Which product line the money came from, resolved when the event was processed. Not the\nsame as `event_kind`: that one says what happened, this one says where it came from.\n`null` when the event does not fall in any category, or when it predates the field.\n"},"money_returned":{"type":"boolean","nullable":true,"description":"Whether money actually went back to the payer through the provider. Always `true` for\na refund. For a credit note it is `true` only when the note settles provider refunds:\na note credited against an unpaid invoice, or settled outside the provider, corrects\nthe same amount without moving any money. `null` when the concept does not apply, as\non a charge.\n"},"customer_email":{"type":"string","nullable":true,"description":"Payer email reported by the provider."},"customer_name":{"type":"string","nullable":true,"description":"Payer name reported by the provider."},"status":{"$ref":"#/components/schemas/PaymentEventStatus"},"invoice_id":{"type":"string","format":"uuid","nullable":true,"description":"Invoice generated from the event, when there is one."},"invoice_number":{"type":"string","nullable":true,"description":"Number of the invoice generated from the event. `null` while that invoice is still a\ndraft — a draft has no number yet — and `null` when the event produced no invoice.\n","example":"2026/0042"},"failure_category":{"allOf":[{"$ref":"#/components/schemas/PaymentEventFailureCategory"}],"nullable":true,"description":"High-level cause of the failure, when the event did not complete. Same vocabulary the\n`failure_category` filter takes.\n"},"failure_reason":{"type":"string","nullable":true,"description":"Stable failure code you can branch on.","example":"MISSING_DEFAULT_SERIES"},"failure_message":{"type":"string","nullable":true,"description":"Readable explanation of the failure, in the language of the request."},"needs_action":{"type":"boolean","description":"Whether the event did not complete and is still worth acting on. Events that failed\nfor reasons outside your control are excluded.\n"},"draft_available":{"type":"boolean","description":"Whether a draft invoice can be generated from this event. When `false`, the draft\noperation would be rejected.\n"},"retry_available":{"type":"boolean","description":"Whether retrying this event would be accepted right now. Do not derive it from\n`status`: a skipped event may still be recoverable, and a failed one with no\nretries left is not.\n"},"discard_available":{"type":"boolean","description":"Whether this event can be discarded — taken out of the list. When `false`, the\ndiscard operation would be rejected.\n\nAn event that is `PROCESSED` or `PROCESSING` cannot be discarded: it has (or is\nabout to have) an invoice that already went through VeriFactu, and hiding it would\nbreak the audit trail. Do not derive it from `status`: read this flag.\n\nAlready-discarded events keep the flag they would have — what applies to them is\nrestore, not discard.\n"},"retry_count":{"type":"integer","description":"Retries charged to the event: those that ended in an outcome attributable to it.\nA retry that failed for a transient cause outside the event is not counted.\n"},"received_at":{"type":"string","format":"date-time","description":"When the event was received."},"processed_at":{"type":"string","format":"date-time","nullable":true,"description":"When processing of the event finished."},"description":{"type":"string","nullable":true,"description":"Charge description as the provider reported it. It is what the description filters of\nthe connection match against, so it is what makes them auditable.\n"},"payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"nullable":true,"description":"How the charge was paid, when the provider reported it. `null` means the provider\ndid not report a method for this event.\n","example":"CARD"},"discarded":{"type":"boolean","description":"Whether the event was discarded (soft-deleted) and is hidden by default."},"discarded_at":{"type":"string","format":"date-time","nullable":true,"description":"When the event was discarded, when it is."}}},"ListManagedPaymentEventsResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["events"],"properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/ManagedPaymentEvent"}},"pagination":{"$ref":"#/components/schemas/Pagination"},"counts":{"$ref":"#/components/schemas/PaymentEventCounts"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"ManagedPaymentEventResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/ManagedPaymentEvent"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"RetryManagedPaymentEventResponse":{"deprecated":true,"description":"Deprecated alias of `ManagedPaymentEventResponse`, kept so generated SDKs do not lose the\ntype name. It has always had the same shape; use `ManagedPaymentEventResponse` instead.\n**Retires on 11 December 2026.** See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","allOf":[{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}]},"ResolveManagedPaymentEventResponse":{"deprecated":true,"description":"Deprecated alias of `ManagedPaymentEventResponse`, kept so generated SDKs do not lose the\ntype name. It has always had the same shape; use `ManagedPaymentEventResponse` instead.\n**Retires on 11 December 2026.** See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","allOf":[{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}]},"DiscardManagedPaymentEventResponse":{"deprecated":true,"description":"Deprecated alias of `ManagedPaymentEventResponse`, kept so generated SDKs do not lose the\ntype name. It has always had the same shape; use `ManagedPaymentEventResponse` instead.\n**Retires on 11 December 2026.** See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","allOf":[{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}]},"RestoreManagedPaymentEventResponse":{"deprecated":true,"description":"Deprecated alias of `ManagedPaymentEventResponse`, kept so generated SDKs do not lose the\ntype name. It has always had the same shape; use `ManagedPaymentEventResponse` instead.\n**Retires on 11 December 2026.** See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","allOf":[{"$ref":"#/components/schemas/ManagedPaymentEventResponse"}]},"ManagedPaymentEventDraftResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["invoice_id"],"properties":{"invoice_id":{"type":"string","format":"uuid","description":"Draft invoice created from the payment event."}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"EmailDeliveryStatus":{"type":"string","enum":["QUEUED","REJECTED","SENT","FAILED","DELIVERED","BOUNCED","OPENED"],"description":"Status of an email.\n\nThe history records every email the system decided to send, not only the ones that\nwent out: an email stopped by policy is listed as REJECTED rather than omitted.\n\n- QUEUED: authorised and recorded, not dispatched yet\n- REJECTED: stopped by policy and never sent (terminal, not retried). In test\n  environments invoices may only be emailed to the account owner's own address\n  (`+tag` aliases included), so a message addressed elsewhere lands here\n- SENT: successfully sent to the provider\n- FAILED: sending failed\n- DELIVERED / BOUNCED / OPENED: reported by the provider's webhooks\n"},"EmailDeliveryResponse":{"type":"object","description":"Email sent by the system on behalf of the user (history).","required":["id","email_type","recipients","status"],"properties":{"id":{"type":"string","format":"uuid"},"email_type":{"type":"string","description":"Email type (EmailType name or template). e.g. INVOICE_EMITTED","example":"INVOICE_EMITTED"},"recipients":{"type":"array","items":{"type":"string"},"description":"Recipient addresses (To)"},"cc":{"type":"array","items":{"type":"string"},"description":"Carbon-copy addresses (CC)"},"subject":{"type":"string"},"related_entity_type":{"type":"string","nullable":true,"description":"Type of the SINGLE related entity (e.g. INVOICE for an invoice,\nUSER, SUBSCRIPTION). `null` for batch emails (BULK_INVOICES): their\nrelation is the invoices in `related_invoices`, not a single entity.\n","example":"INVOICE"},"related_entity_id":{"type":"string","format":"uuid","nullable":true,"description":"Id of the single related entity. `null` for batch emails."},"status":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"sent_at":{"type":"string","format":"date-time","description":"Time the email was sent"}}},"EmailDeliveryListResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["emails"],"properties":{"emails":{"type":"array","items":{"$ref":"#/components/schemas/EmailDeliveryResponse"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"EmailDeliveryDetail":{"type":"object","description":"Detail of one recorded email, including the message content\n(HTML / text body) retrieved live from the email provider.\n\nThe body is only available for emails with a provider id\n(`external_message_id`) and while the provider retains it; when it\ncannot be retrieved, `body_available` is `false` and the bodies are null.\n","required":["id","email_type","recipients","status","body_available"],"properties":{"id":{"type":"string","format":"uuid"},"email_type":{"type":"string","description":"Email type (EmailType name or template). e.g. INVOICE_EMITTED","example":"INVOICE_EMITTED"},"recipients":{"type":"array","items":{"type":"string"},"description":"Recipient addresses (To)"},"cc":{"type":"array","items":{"type":"string"},"description":"Carbon-copy addresses (CC)"},"subject":{"type":"string"},"related_entity_type":{"type":"string","nullable":true,"description":"Type of the SINGLE related entity (e.g. INVOICE for an invoice,\nUSER, SUBSCRIPTION). `null` for batch emails (BULK_INVOICES): their\nrelation is the invoices in `related_invoices`, not a single entity.\n","example":"INVOICE"},"related_entity_id":{"type":"string","format":"uuid","nullable":true,"description":"Id of the single related entity. `null` for batch emails."},"status":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"sent_at":{"type":"string","format":"date-time","description":"Time the email was sent"},"body_available":{"type":"boolean","description":"`true` if the email content could be retrieved from the provider.\n`false` if the email has no provider id or the provider no longer\nretains it.\n"},"html_body":{"type":"string","nullable":true,"description":"HTML body of the email as it was sent. Null if not available."},"text_body":{"type":"string","nullable":true,"description":"Plain-text body of the email. Null if not available."},"attachments":{"type":"array","description":"Email attachments (e.g. the invoice PDF). In invoice emails the actual\ncontent goes here, not in the body.\n","items":{"$ref":"#/components/schemas/EmailAttachment"}},"related_invoices":{"type":"array","description":"Invoices included in a batch email (BULK_INVOICES). Each element links\nto its invoice. Absent/empty in single-entity emails.\n","items":{"$ref":"#/components/schemas/RelatedInvoice"}}}},"EmailAttachment":{"type":"object","description":"Attachment of a delivered email, retrieved from the provider.","required":["filename"],"properties":{"filename":{"type":"string","description":"File name","example":"factura-A-2026-0001.pdf"},"content_type":{"type":"string","description":"MIME type of the attachment","example":"application/pdf"},"size":{"type":"integer","description":"Size in bytes, if known"},"download_url":{"type":"string","nullable":true,"description":"Temporary download URL signed by the provider (may expire). Null\nif the provider does not supply it.\n"}}},"RelatedInvoice":{"type":"object","description":"Reference to an invoice included in an email (e.g. each invoice in a batch).","required":["id","invoice_number"],"properties":{"id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","example":"A-2025-0002"}}},"EmailDeliveryDetailResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/EmailDeliveryDetail"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"EmailDeliveryIndicator":{"type":"object","description":"Summary of the emails recorded for an entity (e.g. an invoice), attempts included.\nUsed to show an \"email sent\" indicator next to each entity without\nloading the full history.\n","required":["related_entity_id","count"],"properties":{"related_entity_id":{"type":"string","format":"uuid","description":"Id of the related entity (e.g. invoice_id)"},"count":{"type":"integer","description":"Number of emails recorded for the entity, whatever their outcome","example":2},"last_status":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"last_sent_at":{"type":"string","format":"date-time","description":"Time of the last email sent for the entity"}}},"EmailDeliveryIndicatorListResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["indicators"],"properties":{"indicators":{"type":"array","items":{"$ref":"#/components/schemas/EmailDeliveryIndicator"}}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"QueriedPeriod":{"type":"object","description":"Information about the queried period","properties":{"start_date":{"type":"string","format":"date","description":"Period start date","example":"2025-01-01"},"end_date":{"type":"string","format":"date","description":"Period end date","example":"2025-03-31"},"days_included":{"type":"integer","description":"Number of days included in the period","example":90}},"required":["start_date","end_date","days_included"]},"TaxBreakdownItem":{"type":"object","description":"Tax breakdown by type (IVA, IGIC, IPSI) and rate","properties":{"tax_type":{"type":"string","description":"Tax type: IVA, IGIC, IPSI, OTHER","enum":["IVA","IGIC","IPSI","OTHER"],"example":"IVA"},"percentage":{"type":"number","format":"double","description":"Tax rate percentage","example":21},"taxable_base":{"type":"number","format":"double","description":"Taxable base for this tax type/rate","example":10000},"tax_amount":{"type":"number","format":"double","description":"Tax amount (base * rate / 100)","example":2100},"regime_key":{"type":"string","example":"01","description":"VeriFactu regime key of the rows grouped here. Rows are grouped by\n(tax type, rate, regime key), so an invoice mixing general-regime and OSS lines\nat the same rate yields TWO rows at `percentage: 21` that only this field tells\napart (`01` vs `17`). Index by `(tax_type, percentage, regime_key)`, never by\n`(tax_type, percentage)` alone.\n"}},"required":["tax_type","percentage","taxable_base","tax_amount"]},"SurchargeBreakdownItem":{"type":"object","description":"Equivalence surcharge breakdown by rate. Same shape as `surcharge_breakdown` in the invoice detail.","properties":{"type":{"type":"number","format":"double","description":"Surcharge rate percentage","example":5.2},"base":{"type":"number","format":"double","description":"Taxable base subject to this surcharge rate","example":1000},"amount":{"type":"number","format":"double","description":"Surcharge amount (base * rate / 100)","example":52}},"required":["type","base","amount"]},"IrpfBracket":{"type":"object","description":"IRPF tax bracket with calculated values","properties":{"base_from":{"type":"number","format":"double","description":"Lower bound of taxable base","example":0},"base_to":{"type":"number","format":"double","nullable":true,"description":"Upper bound of taxable base (null for unlimited)","example":12450},"rate_percentage":{"type":"number","format":"double","description":"Tax rate percentage for this bracket","example":19},"applicable_base":{"type":"number","format":"double","description":"Portion of income falling in this bracket","example":12450},"amount":{"type":"number","format":"double","description":"Tax amount for this bracket","example":2365.5}},"required":["base_from","rate_percentage","applicable_base","amount"]},"InvoiceFiscalData":{"type":"object","description":"Invoice fiscal data for summary","properties":{"id":{"type":"string","format":"uuid","description":"Invoice ID"},"invoice_number":{"type":"string","description":"Invoice number","example":"F2025/001"},"issue_date":{"type":"string","format":"date","description":"Issue date","example":"2025-01-15"},"customer_name":{"type":"string","description":"Customer name","example":"Empresa SL"},"taxable_base":{"type":"number","format":"double","description":"Taxable base","example":1000},"total_vat":{"type":"number","format":"double","description":"Total indirect tax amount (IVA, IGIC, IPSI and other rates), not only VAT. See tax_breakdown for the split by tax type.","example":210},"total_irpf":{"type":"number","format":"double","description":"IRPF withholding amount","example":150},"invoice_total":{"type":"number","format":"double","description":"Total invoice amount","example":1060}},"required":["id","invoice_number","issue_date","taxable_base","total_vat","total_irpf","invoice_total"]},"FiscalSummaryResponse":{"type":"object","description":"Comprehensive fiscal summary for a period","properties":{"queried_period":{"$ref":"#/components/schemas/QueriedPeriod"},"total_taxable_base":{"type":"number","format":"double","description":"Total taxable base for the period","example":15000},"total_vat":{"type":"number","format":"double","description":"Total indirect tax for the period (IVA, IGIC, IPSI and other rates), not only VAT. See tax_breakdown for the split by tax type.","example":3150},"tax_breakdown":{"type":"array","items":{"$ref":"#/components/schemas/TaxBreakdownItem"},"description":"Tax breakdown by type (IVA, IGIC, IPSI) and rate. Allows a single invoice to have multiple tax types.","example":[{"tax_type":"IVA","percentage":21,"taxable_base":8000,"tax_amount":1680},{"tax_type":"IGIC","percentage":7,"taxable_base":2000,"tax_amount":140}]},"vat_breakdown_by_rate":{"type":"object","additionalProperties":{"type":"number","format":"double"},"deprecated":true,"description":"DEPRECATED: Use tax_breakdown instead.\n\nIndirect tax breakdown by rate (rate -> amount). It aggregates EVERY indirect tax\n(IVA, IGIC, IPSI and other rates) by percentage alone, so two different tax types at\nthe same rate collapse into a single entry and cannot be told apart. The rate key is\nalways rendered with three decimals (\"21.000\", \"0.000\").\n\n`tax_breakdown` is the replacement: it keeps `tax_type` and `percentage` separate.\n","example":{"21.000":3150}},"surcharge_breakdown":{"type":"array","items":{"$ref":"#/components/schemas/SurchargeBreakdownItem"},"description":"Equivalence surcharge breakdown by rate, aggregated over the period. Same shape as\n`surcharge_breakdown` in the invoice detail. Empty (or absent) when no invoice in the\nperiod carries an equivalence surcharge.\n","example":[{"type":5.2,"base":1000,"amount":52}]},"total_equivalence_surcharge":{"type":"number","format":"double","description":"Total equivalence surcharge for the period. It is NOT part of `total_vat`: the surcharge\nis settled by the retailer, not by the issuer, and `total_vat` only carries the main\nindirect tax.\n","example":52},"total_irpf_withheld":{"type":"number","format":"double","description":"Total IRPF withheld in the period","example":750},"period_base":{"type":"number","format":"double","description":"Taxable base for the period","example":15000},"projected_annual_base":{"type":"number","format":"double","description":"Projected annual taxable base","example":60000},"estimated_annual_irpf":{"type":"number","format":"double","description":"Estimated annual IRPF based on projected income","example":14582.5},"pending_annual_irpf":{"type":"number","format":"double","description":"Pending annual IRPF (estimated - retained)","example":11546.17},"bracket_details":{"type":"array","items":{"$ref":"#/components/schemas/IrpfBracket"},"description":"IRPF breakdown by tax brackets"},"invoices":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceFiscalData"},"description":"List of invoices included in the calculation"},"total_invoices":{"type":"integer","description":"Total number of invoices","example":12}},"required":["queried_period","total_taxable_base","total_vat","total_irpf_withheld","period_base","projected_annual_base","estimated_annual_irpf","pending_annual_irpf","bracket_details","total_invoices"]},"Environment":{"type":"string","description":"Mode a record lives in — its Test/Live twin. It decides where invoices, customers and\nquota are accounted.\n\nFor a company it also decides which AEAT its NIF is registered against: switching a\ncompany on in `PROD` is what registers it with the real AEAT, so `aeat_environment`\nis that same mode and uses this same enum.\n","enum":["TEST","PROD"],"example":"PROD"},"MyCredential":{"type":"object","description":"The credential this call was authenticated with. Lets a client discover, in a single request, what it is allowed to do and which environment it operates on, instead of learning it from a `403`.","required":["type","environment","scopes"],"properties":{"type":{"type":"string","description":"How the caller authenticated.\n\n* `api_key` - a secret key sent as a bearer token.\n* `oauth_token` - an access token issued by the authorisation server.\n* `session` - a browser session cookie.\n","enum":["api_key","oauth_token","session"],"example":"api_key"},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"},{"description":"Environment every request made with this credential operates on. For a key it is fixed by its prefix; for a session it follows the `BeeL-Environment` header."}]},"scopes":{"type":"array","description":"Permissions granted to this credential, each named after the resource it covers and the action it allows. For a key or token these are the scopes it was issued with; for a session they are the permissions its holder's role grants on the account and on the company in focus. Empty means the credential can only read its own identity.","items":{"type":"string","example":"invoices:write"},"example":["invoices:write","customers:read","companies:list"]}}},"MyIdentity":{"type":"object","description":"Minimal display identity of the authenticated principal (whoami).","required":["account_id","email"],"properties":{"account_id":{"allOf":[{"$ref":"#/components/schemas/UUID"},{"description":"The account the credential belongs to. It identifies who you are, not what you are currently looking at: selecting a different company with `BeeL-Active-Company` does not change it, so a credential health-check can safely key on it."}]},"name":{"type":"string","maxLength":255,"nullable":true,"description":"trade_name ?? legal_name of the active fiscal profile. Null pre-onboarding.","example":"BeeL.es"},"email":{"$ref":"#/components/schemas/Email"},"logo_url":{"type":"string","maxLength":500,"nullable":true,"description":"URL of the active fiscal profile logo, or null.","example":"https://assets.beel.es/logos/.../profile-logo.jpg"},"language":{"allOf":[{"$ref":"#/components/schemas/Language"},{"description":"Interface language of the person behind the credential. It is a preference of the person, not of a fiscal profile: the language of invoices and of e-mails are separate settings of each company."}]},"credential":{"$ref":"#/components/schemas/MyCredential"}}},"UpdateMeRequest":{"type":"object","description":"Mutable preferences of the authenticated person.","required":["language"],"properties":{"language":{"$ref":"#/components/schemas/Language"}}},"MyPreferences":{"type":"object","description":"Preferences of the authenticated person (person axis, not company).","required":["language"],"properties":{"language":{"$ref":"#/components/schemas/Language"}}},"SortOrder":{"type":"string","description":"Sort direction. Shared vocabulary for every `sort_order` query param: declared once so the\ngenerator emits a real enum and an unknown direction is rejected with `400` instead of being\nsilently ignored.\n","enum":["asc","desc"],"example":"desc"},"ExemptionReason":{"type":"string","enum":["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"],"description":"Tax exemption reason code per the Spanish VAT Law (Ley 37/1992, LIVA), with the\nVeriFactu code each one is reported as.\n\n- `EXENTA_ART_20`: exempt, art. 20 (domestic operations such as medical, educational,\n  cultural and financial services, or housing rentals). E1.\n- `EXENTA_ART_21`: exempt, art. 21 (exports of goods). E2.\n- `EXENTA_ART_22`: exempt, art. 22 (operations treated as exports). E3.\n- `EXENTA_ART_24`: exempt, art. 24 (free zones, warehouses and customs regimes). E4.\n- `EXENTA_ART_25`: exempt, art. 25 (intra-community supplies of goods). E5.\n- `EXENTA_ART_26`: exempt, art. 26 (intra-community acquisitions of goods). It exempts the\n  buyer's acquisition, not a supply the seller invoices, so an invoice line that carries it\n  is rejected with `EXEMPTION_NOT_FOR_ISSUED_INVOICE`; a supply to another Member State is\n  `EXENTA_ART_25`.\n- `NO_SUJETA_ART_7_9`: not subject under art. 7 (such as the transfer of a business as\n  a going concern, art. 7.1º). N1.\n- `NO_SUJETA_LOCALIZACION`: not subject by the place-of-supply rules (intra-community\n  or non-EU services, arts. 69 and 70). N2.\n- `ISP_ART_84_2_A` … `ISP_ART_84_2_F`: reverse charge (the invoice states «inversión del\n  sujeto pasivo»), art. 84.Uno.2.º letters a) (supplier not established in Spain), b) (unwrought\n  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\n  allowances), e) (certain real estate supplies: in insolvency proceedings, with the exemption\n  waived, or enforcing a security) and f) (construction or renovation works). S2.\n- `ISP_ART_84_2_G`: reverse charge of letter g) (silver, platinum, palladium, mobile phones,\n  consoles, laptops and tablets). The law requires these supplies to be invoiced in a special\n  series, so an invoice line that carries it is rejected with\n  `REVERSE_CHARGE_CASE_NOT_SUPPORTED`.\n- `EXENTA_ART_140`: investment gold exemption, art. 140 bis (usually with `regime_key`\n  `04`). E6.\n- `REGIMEN_ART_129` (agriculture,\n  livestock and fishing, arts. 124 to 134 bis), `REGIMEN_ART_135` (second-hand goods,\n  art and antiques), `REGIMEN_ART_141` (travel agencies), `REGIMEN_ART_154` (equivalence\n  surcharge) and `REGIMEN_ART_163_DECIES` (cash basis, arts. 163 decies to 163\n  sexiesdecies): operations of special regimes, which VeriFactu identifies by the regime\n  key rather than by an exemption code. An\n  invoice line that carries one is rejected with `EXEMPTION_REGIME_NOT_SUPPORTED_IN_VERIFACTU`;\n  declare the regime with `regime_key` instead.\n- `OTRO`: any other provision. Requires the text in `exemption_reason_text`. E6.\n","example":"EXENTA_ART_20"},"InvoiceLineType":{"type":"string","description":"Fiscal line type. `NORMAL` contributes to the taxable base and VAT;\n`SUPLIDO` is a payment made on behalf of the client and is excluded from both.\n","enum":["NORMAL","SUPLIDO"],"example":"NORMAL"},"VoidCause":{"type":"string","enum":["VOID_REQUEST","TOTAL_CORRECTIVE","EXCHANGED"],"description":"Why a `VOIDED` invoice reached that status:\n- VOID_REQUEST: Voided directly via `POST /v1/invoices/{invoice_id}/void`. The\n  original VeriFactu record is cancelled with the tax authority.\n- TOTAL_CORRECTIVE: Voided as a result of issuing a TOTAL corrective invoice over\n  it. The original VeriFactu record stays untouched; the corrective invoice is\n  reported as a new record instead.\n- EXCHANGED: A simplified invoice replaced by a full invoice issued in exchange for it\n  (`replaced_invoice_ids` of that invoice). Its VeriFactu record is not cancelled: the\n  exchange invoice is recorded as `F3`, identifying it as replaced.\n\nOnly present on voided invoices.\n"},"VeriFactuSkipReason":{"type":"string","description":"Reason why an automatically generated invoice was not submitted to AEAT.\n\nPresent only when a submission was expected and was omitted, so an invoice that\nwas skipped can be told apart from one that never had to be submitted. Every\nvalue is resolved by the issuer from their own settings.\n\n* `CONNECTION_DISABLED` — automatic submission is turned off on the payment connection that produced the invoice.\n* `CONFIG_MISSING` — the issuing tax ID has no VeriFactu configuration.\n* `CONFIG_DISABLED` — VeriFactu is disabled for the issuing tax ID.\n* `NIF_NOT_REGISTERED` — the issuing tax ID is not yet registered with AEAT, so the submission would be rejected.\n","enum":["CONNECTION_DISABLED","CONFIG_MISSING","CONFIG_DISABLED","NIF_NOT_REGISTERED"],"example":"CONFIG_DISABLED"},"VeriFactuErrorCode":{"type":"string","nullable":true,"description":"Error code of the outcome. When the AEAT reported a remark or an error on the record,\nit is the AEAT's own code (numeric, for example `1105`), on a rejection (`REJECTED`)\nand also on an acceptance with errors (`ACCEPTED_WITH_ERRORS`). When the submission was\nrejected before reaching the AEAT for a reason BeeL. recognises, it is a BeeL. code:\n\n* `RECIPIENT_VAT_NOT_IN_VIES` — the customer's VAT number is not registered in VIES.\n* `RECIPIENT_VAT_INVALID_FORMAT` — the customer's VAT number does not have a valid\n  format.\n* `RECIPIENT_VAT_COUNTRY_NOT_EU` — the country of the customer's VAT number is not an EU\n  member state.\n* `ISSUE_DATE_NOT_SUBMISSION_DATE` — the issue date is not the date of the submission.\n* `INVOICE_NUMBER_TAKEN_IN_AEAT` — the AEAT already has this invoice's number and issue\n  date for a different invoice.\n\nA `REJECTED` can arrive without it: `error_message` always says why.\n","example":"3000"},"InvoiceAttachment":{"type":"object","description":"A file attached to the invoice.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string"},"type":{"type":"string"}}},"InvoiceSendRecord":{"type":"object","description":"One email through which an invoice was sent, as recorded in the delivery\nread-model. Batch sends (one email carrying several invoices) produce one\nrecord in each of the invoices they carry.\n","required":["id","recipients","status"],"properties":{"id":{"type":"string","format":"uuid","description":"Id of the delivery record (same id as in `GET /v1/emails`)."},"recipients":{"type":"array","items":{"type":"string"},"description":"Recipient addresses (To)"},"cc":{"type":"array","items":{"type":"string"},"description":"Carbon-copy addresses (CC)"},"subject":{"type":"string"},"status":{"$ref":"#/components/schemas/EmailDeliveryStatus"},"sent_at":{"type":"string","format":"date-time","description":"Moment the email provider ACCEPTED the message — not the moment it\nreached the mailbox. Later outcomes (delivered, bounced, opened) are\nreflected in `status` as the provider reports them.\n\nAbsent while there is no such moment: the history records decisions,\nand a `QUEUED` record has not been dispatched yet, a `REJECTED` one\nnever will be, and a `FAILED` one never got that far. Read `status`\nto tell those apart; do not read an absent `sent_at` as \"sent long\nago\". It is omitted, never sent as `null`.\n","example":"2025-01-29T18:45:00Z"},"external_message_id":{"type":"string","nullable":true,"description":"Message id at the email provider, when available."},"error":{"type":"string","nullable":true,"description":"Why the email did not go out, present only when `status` is `FAILED` or `REJECTED`.\nA short explanation in the language of the request, meant to be shown to a person;\ndo not parse it; branch on `status` instead.\n"}}},"InvoiceEmailDeliveryOutcome":{"type":"object","description":"What became of the invoice's automatic email in the act that produced this response.\n\nOnly present in the response to issuing an invoice (`POST .../invoices/{invoice_id}/issue`).\nIssuing is a fiscal act and never fails because of the email, so a send the sending\npolicy refuses still answers `200` — this object is how it says so. Without it, a\nrefused send and an invoice that never asked for one looked identical.\n","required":["status"],"properties":{"status":{"type":"string","enum":["SENT","REJECTED","NOT_REQUESTED"],"description":"- `SENT` — the email was authorised and accepted for delivery. Delivery itself is\n  asynchronous; follow it in `sending_history`.\n- `REJECTED` — the sending policy refused it. Nothing was queued and nothing will be\n  retried; the refusal is recorded in the delivery ledger.\n- `NOT_REQUESTED` — the invoice does not send automatically.\n","example":"REJECTED"},"reason":{"type":"string","nullable":true,"description":"Translation key explaining a `REJECTED` outcome, deliberately generic. Null for the\nother statuses.\n","example":"error.email.envio_no_permitido"}}},"InvoiceBase":{"type":"object","description":"The shape of an invoice, shared by the persisted resource and by the computed\npreview of one. It does not require the three fields that only a stored row can\nhave — `id`, `created_at` and `updated_at`. Read `Invoice` or `NextOccurrence`,\nnever this one: it is not the payload of any operation.\n","required":["series","type","status","issue_date","issuer","recipient","lines","totals"],"properties":{"invoice_number":{"type":"string","nullable":true,"example":"2025/0001","description":"Complete invoice number (series + sequential).\n**Null for draft invoices** — assigned automatically when issued.\n"},"series":{"$ref":"#/components/schemas/SeriesInfo"},"number":{"type":"integer","nullable":true,"example":1,"description":"Sequential number within the series.\n**Null for draft invoices** — assigned automatically when issued.\n"},"type":{"$ref":"#/components/schemas/InvoiceType"},"status":{"$ref":"#/components/schemas/InvoiceStatus"},"issue_date":{"type":"string","format":"date","description":"Invoice issue date. Always set to the current date when the invoice is created.\nIf the operation occurred on a different date, use `operation_date`.\n","example":"2025-01-15"},"operation_date":{"type":"string","format":"date","nullable":true,"description":"Date when the operation actually occurred. Used when invoicing for a past operation.\nIf null, the operation date is the same as the issue date.\n","example":"2025-01-10"},"due_date":{"type":"string","format":"date","description":"Payment due date (must be the same as or after `issue_date`)","example":"2025-02-14"},"valid_until":{"type":"string","format":"date","nullable":true,"description":"Offer validity date. Only rendered on PROFORMA invoices; on any other\ninvoice type the field is inert (accepted and stored, but never shown on\nthe document). Purely informational — nothing is triggered automatically\nwhen it passes. Not to be confused with `due_date`, the payment due date.\n","example":"2025-02-28"},"payment_date":{"type":"string","format":"date","description":"Business date when the payment was received (e.g., the date on the bank statement).\nSet by the user when marking the invoice as paid. An invoice issued already paid gets its\n`issue_date`: one whose `total_to_pay` is 0, or one issued from a payment already\nconfirmed by a payment integration. Present once a payment has been recorded; it stays through\nlater status changes (RECTIFIED, VOIDED).\nContrast with `paid_at`, which is the system timestamp of when the status change was recorded.\n","example":"2025-01-20"},"sent_at":{"type":"string","format":"date-time","nullable":true,"description":"Moment the email provider ACCEPTED the invoice email — **not** the moment\nit reached the recipient's mailbox. Present when status is SENT or later.\n\nWhat happened afterwards (delivered, bounced, opened) is not a single\ntimestamp: it lives in `sending_history`, one record per email with its\nown status and timestamp. On a resend, `sent_at` moves to the latest\naccepted send while `sending_history` keeps every one of them.\n","example":"2025-01-29T18:45:00Z"},"paid_at":{"type":"string","format":"date-time","nullable":true,"description":"System timestamp when the payment was recorded in the system.\nAutomatically set when the invoice status changes to PAID.\nContrast with `payment_date`, which is the business date chosen by the user.\n","example":"2025-02-05T10:30:00Z"},"auto_emit_after":{"type":"string","format":"date","nullable":true,"description":"Date when this draft will be auto-emitted if not manually issued.\nOnly present for drafts created from recurring invoices with `draft_in_advance`\nenabled.\n","example":"2025-03-20"},"scheduled_for":{"type":"string","format":"date","nullable":true,"description":"Date when the invoice should be automatically processed.\nOnly present when status is SCHEDULED.\n","example":"2025-02-15"},"scheduled_action":{"$ref":"#/components/schemas/GenerationAction"},"issuer":{"$ref":"#/components/schemas/IssuerData"},"recipient":{"$ref":"#/components/schemas/RecipientData"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/InvoiceLine"},"description":"Invoice lines. Can be empty: drafts may not have lines yet, and a\nhandful of legacy imported invoices were recorded without them.\nCreating an invoice still requires at least one line.\n"},"totals":{"$ref":"#/components/schemas/InvoiceTotals"},"payment_info":{"$ref":"#/components/schemas/PaymentInfo"},"notes":{"type":"string","maxLength":1000,"description":"Additional observations or notes"},"replaced_invoice_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"Only on a full invoice issued in exchange for simplified invoices: the simplified\ninvoices it replaces, each now `VOIDED` with `void_cause` `EXCHANGED`. With VeriFactu,\nthe invoice is recorded as `F3` identifying them.\n"},"void_cause":{"$ref":"#/components/schemas/VoidCause"},"void_reason":{"type":"string","maxLength":1000,"description":"Reason recorded when the invoice was voided (only for voided invoices)."},"voided_at":{"type":"string","format":"date-time","nullable":true,"description":"System timestamp when the invoice was voided. Automatically set at the moment the\nvoid takes place and never supplied by the caller — a void cannot be dated, so the\ndeprecated `void_date` field of the void request has no effect on it.\n\nInvoices voided before this field existed carry the day they were voided on with a\ntime of `00:00Z`, because only the day was retained for them.\n","example":"2025-01-20T09:12:44Z"},"rectified_invoice_id":{"type":"string","format":"uuid","description":"UUID of the invoice being rectified (only for corrective invoices)"},"source_proforma_id":{"type":"string","format":"uuid","description":"UUID of the source proforma this invoice was converted from\n(only for invoices created via `convert-to-invoice`).\n"},"converted_invoice_id":{"type":"string","format":"uuid","description":"UUID of the live (non-deleted) invoice this proforma was converted into —\nthe inverse of `source_proforma_id`, derived at read time (not persisted).\nOnly present on the detail endpoint (`GET /v1/invoices/{invoice_id}`) for a proforma\nin `CONVERTED` status; never included in list rows.\n"},"rectification_reason":{"type":"string","maxLength":500,"description":"Reason for rectification (only for corrective invoices)"},"recurring_invoice_id":{"type":"string","format":"uuid","nullable":true,"description":"UUID of the recurring invoice that generated this invoice (if any)"},"recurring_invoice_name":{"type":"string","nullable":true,"description":"Name of the recurring invoice (denormalized for display)"},"rectification_type":{"$ref":"#/components/schemas/RectificationType"},"rectification_code":{"$ref":"#/components/schemas/VeriFactuRectificationCode"},"external_ref":{"type":"string","maxLength":255,"nullable":true,"description":"Client-supplied external reference set at creation (order/cart/contract id).","example":"ORD-2025-0042"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata in key-value format. Invoices auto-generated from a\nconnected payment platform carry system keys you can filter on:\n- external_customer_id: Payment-platform customer (e.g. Stripe `cus_…`), present when the payment carried a customer (absent on flows with no customer, e.g. Terminal / payment links without customer collection)\n- external_payment_id: Canonical payment reference. On Stripe this is always the PaymentIntent id (`pi_…`); the Charge, Stripe Invoice and Checkout Session ids are never used here, so every event of the same payment carries the same value.\n- payment_intent_id: Stripe PaymentIntent id, when the payment has one\n- charge_id: Stripe Charge id, when the payment has one\n- payment_provider: Origin platform (e.g. STRIPE_CONNECT)\nPlus any keys you set yourself on manually-created invoices (order ids, tenants, …).\nSee the \"Filtering by metadata\" guide for the full list and query rules.\n","example":{"external_customer_id":"cus_ULGk8bzIr88aag","external_payment_id":"pi_3NqFGb2eZvKYlo2C0z1234AB","payment_intent_id":"pi_3NqFGb2eZvKYlo2C0z1234AB","charge_id":"ch_3NqFGb2eZvKYlo2C1234CDEF","payment_provider":"STRIPE_CONNECT","external_order_id":"ORD-2025-0042"}},"send_automatically":{"type":"boolean","nullable":true,"description":"Whether the invoice will be automatically sent by email after issuing.\nOnly relevant for DRAFT and SCHEDULED invoices.\n"},"email_config":{"allOf":[{"$ref":"#/components/schemas/EmailConfiguration"}],"nullable":true,"description":"Email configuration used when `send_automatically` is true.\nIf it names no recipients, the email goes to the customer's `billing_emails`, or to\nthe customer's `email` when there are none.\n"},"pdf_download_url":{"type":"string","nullable":true,"description":"Relative URL of the endpoint that returns the PDF download link. Relative to the\nAPI base URL (e.g., https://app.beel.es/api). Note it is a link to a link: calling\nit returns a pre-signed URL that expires in five minutes.\n\nNull while there is no PDF to link to: they are produced asynchronously after\nissuing, so poll until the field appears. It is also null on a handful of very old\ninvoices that have no downloadable PDF at all.\n","example":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/550e8400-e29b-41d4-a716-446655440000/pdf"},"verifactu":{"$ref":"#/components/schemas/VeriFactu"},"attachments":{"type":"array","description":"Files attached to the invoice, reserved for per-invoice attachments.\n\nTo send the supporting invoices of a SUPLIDO consolidation, use\n`options.attach_source_invoices` when issuing: they travel as a ZIP attached to\nthe outgoing email, and appear on the email delivery record rather than here.\n","items":{"$ref":"#/components/schemas/InvoiceAttachment"}},"sending_history":{"type":"array","description":"Emails through which this invoice was sent, oldest first. Resending\nappends a record, it never replaces the previous one, and a batch send\n(one email with several invoices) is recorded in every invoice it carried.\n\nOnly populated in single-invoice responses (`GET /v1/invoices/{invoice_id}` and\nthe lifecycle endpoints); the list endpoint omits it.\n","items":{"$ref":"#/components/schemas/InvoiceSendRecord"}},"email_delivery":{"$ref":"#/components/schemas/InvoiceEmailDeliveryOutcome"},"deleted_at":{"type":"string","format":"date-time","nullable":true}}},"PhoneInput":{"type":"string","minLength":9,"maxLength":20,"pattern":"^[+]?[0-9\\s\\-\\(\\)]+$","description":"A phone number as this API accepts it: 9 to 20 characters, and only digits, spaces,\ndashes, parentheses and an optional leading `+`. Every request that takes a phone number\nuses this schema.\n\nIt is `Phone` plus the rules enforced on input. A value this schema accepts always\nsatisfies `Phone`, so anything you send here is something a response can return.\n","example":"+34 612 345 678"},"ExternalRef":{"type":"string","maxLength":255,"description":"Client-supplied identifier from an external system (order, cart, contract…).\nStored as-is, echoed back on read, and filterable via GET /v1/invoices?external_ref=.\nOptional. Enforced UNIQUE per issuer for live standard/simplified invoices:\ncreating a second invoice with the same reference returns 409\n(INVOICE_DUPLICATE_EXTERNAL_REFERENCE); deleting the existing one lets you recreate.\nCorrective invoices are exempt from that uniqueness: a corrective carries the same\norder reference as the invoice it corrects, so both can coexist.\nThis is a business key, NOT the Idempotency-Key (which guards request retries).\n","example":"ORD-2025-0042"},"InvoiceMetadata":{"type":"object","additionalProperties":true,"description":"Your own key/value pairs to cross-reference this invoice with records in\nyour system (order ids, tenants, internal codes). Namespace them to avoid\nclashing with the system keys BeeL adds on payment-generated invoices.\n","example":{"external_order_id":"ORD-2025-0042","tenant":"acme","project_code":"PROJ-123"}},"BulkOperationResult":{"type":"object","description":"Outcome of a bulk operation processed invoice by invoice.","required":["total","successful","failed"],"properties":{"total":{"type":"integer","description":"Total number of invoices processed","example":5},"successful":{"type":"integer","description":"Number of invoices processed successfully","example":4},"failed":{"type":"integer","description":"Number of invoices that failed","example":1},"failures":{"type":"array","description":"Details of the invoices that failed","items":{"type":"object","required":["invoice_id","reason"],"properties":{"invoice_id":{"$ref":"#/components/schemas/UUID"},"reason":{"type":"string","description":"Reason for the failure","example":"Only draft invoices can be issued"}}}}}},"BulkOperationResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/BulkOperationResult"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"VoidInvoiceRequest":{"type":"object","required":["reason"],"properties":{"reason":{"type":"string","minLength":10,"maxLength":500,"description":"Void reason (minimum 10 characters)","example":"Duplicate of invoice F-2026-0142, issued twice by mistake"},"issued_in_error":{"type":"boolean","default":false,"description":"Confirms that the invoice was issued by mistake: the operation it describes never took\nplace, it was a test, or it is an accidental duplicate. A void is only for those cases\n(RD 1007/2023, art. 11.1); an operation that did take place is corrected with a\ncorrective invoice.\n\nRequired as `true` when the invoice has already been emailed to the customer or paid —\ndelivering or collecting it suggests the operation was real, so the void has to say it\nwas not. It is the fact that counts, not the current `status`: an email in the\ninvoice's sending history, or a `paid_at`, still require it after the invoice moves\nback to `ISSUED` or becomes `RECTIFIED`.\nWithout it such a void fails with `422 VOID_REQUIRES_ISSUED_IN_ERROR`.\n"},"void_date":{"type":"string","format":"date","deprecated":true,"description":"**Deprecated.** The void is recorded with the instant it actually takes place,\nreturned as `voided_at` on the invoice: a void cannot be dated by the caller, and this\nvalue never changes `voided_at`. It will be removed in a future version. A date earlier\nthan the invoice's issue date is rejected with `422 VOID_DATE_BEFORE_ISSUE_DATE`.\n","example":"2025-01-20"}}},"ConvertProformaToInvoiceRequest":{"type":"object","properties":{"issue":{"type":"boolean","default":false,"description":"If `true`, emit the resulting invoice atomically in the same act\n(assigns a fiscal number and runs the quota/ledger/VeriFactu→PDF flow).\nIf `false` or omitted, the invoice is left in `DRAFT`.\n","example":false}}},"InvoiceDerivationMode":{"type":"string","enum":["DUPLICATE"],"description":"How to derive the new invoice from `from_invoice_id`.\n\n- **DUPLICATE**: copy an existing invoice into a fresh draft. The source invoice is not\n  modified.\n\nTurning a proforma into an invoice is **not** a derivation mode: it is a fiscal act that\nnumbers a document and moves the source proforma to a terminal status, so it keeps its own\ndedicated operation.\n"},"CreateInvoiceDerivationRequest":{"type":"object","description":"Derives a new draft invoice from an existing one. It is a sibling sub-resource of\n`invoices` and not a variant of the create request on purpose: this call does not describe\nan invoice, it names one, so it carries neither `type`, nor `recipient`, nor `lines`.\nEverything the new draft needs is copied from the source invoice, which is left untouched.\n","required":["from_invoice_id","mode"],"properties":{"from_invoice_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Invoice this one is derived from. It must belong to the company in the path; an\ninvoice you cannot reach is reported the same way as one that does not exist.\n"},"mode":{"$ref":"#/components/schemas/InvoiceDerivationMode"},"series_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Series for the new draft. Defaults to the series of the source invoice."},"notes":{"type":"string","maxLength":2000,"description":"Observations for the new draft. Defaults to those of the source invoice."}}},"FieldDeserializationError":{"type":"object","required":["field","invalid_value"],"properties":{"field":{"type":"string","description":"JSON field name that caused the error (matches the API contract name)","example":"due_date"},"invalid_value":{"type":"string","description":"The value that was rejected. When the property carries a JSON value of the wrong type (a string where an array is expected, an object where a number is expected), this is the kind of value that was received — `string`, `number`, `boolean`, `array`, `object` or `null` — not its content, and `expected_format` names the kind the property takes.","example":"2026-03-04fds"},"expected_format":{"type":"string","nullable":true,"description":"Expected format hint (e.g. YYYY-MM-DD for dates)","example":"YYYY-MM-DD"},"allowed_values":{"type":"string","nullable":true,"description":"Comma-separated list of allowed values (for enum fields)","example":"STANDARD, SIMPLIFIED, CORRECTIVE"}}},"CreateInvoiceBatchRequest":{"type":"object","description":"Applies one operation to a set of invoices of this company. Only the operations that share\na result shape live here; downloading PDFs, sending email and exporting have their own\nsibling sub-resources because each returns something different.\n","required":["operation","invoice_ids"],"properties":{"operation":{"type":"string","enum":["ISSUE","STATUS"],"description":"- **ISSUE**: issue the draft invoices, each one assigned its definitive number.\n- **STATUS**: change the status of the invoices; requires `new_status`.\n"},"invoice_ids":{"type":"array","minItems":1,"maxItems":50,"items":{"$ref":"#/components/schemas/UUID"}},"new_status":{"type":"string","enum":["SENT","PAID"],"description":"Target status. Required when `operation` is `STATUS`."},"payment_date":{"type":"string","format":"date","description":"Payment date. Required when `new_status` is `PAID`.","example":"2025-01-15"}}},"CreateInvoicePdfArchiveRequest":{"type":"object","required":["invoice_ids"],"properties":{"invoice_ids":{"type":"array","description":"Invoices whose PDFs go into the archive.","minItems":1,"maxItems":500,"items":{"$ref":"#/components/schemas/UUID"}}}},"CreateInvoiceDeliveryRequest":{"type":"object","description":"One email delivery carrying several invoices of this company as attachments.","required":["invoice_ids","recipients"],"properties":{"invoice_ids":{"type":"array","minItems":1,"maxItems":200,"items":{"$ref":"#/components/schemas/UUID"}},"recipients":{"type":"array","description":"Email recipients. At least one is required: no address is inferred from any profile.\n","minItems":1,"items":{"$ref":"#/components/schemas/Email"}},"cc":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"CC recipients. Copied addresses count as recipients of the message: they are subject\nto the same sending restrictions and to the same quota as the addresses in `recipients`.\nWhen omitted, the CC addresses configured in the sender's email defaults apply; send an\nempty array to deliver the message without any copy.\n"},"subject":{"type":"string","description":"Custom email subject. Defaults to the standard template.","maxLength":200},"message":{"type":"string","description":"Custom message body. Defaults to the standard template.","maxLength":2000},"language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"description":"Email language. Defaults to the language of the requesting user."}}},"InvoiceExportFormat":{"type":"string","enum":["SUMMARY","ITEMS"],"description":"- **SUMMARY**: one row per invoice with aggregated totals.\n- **ITEMS**: one row per invoice line.\n"},"InvoiceExportFilters":{"type":"object","description":"Selection criteria used when `invoice_ids` is not supplied. Max 50000 invoices: a wider match is rejected with `422 EXPORT_LIMIT_EXCEEDED`, never truncated.","properties":{"status":{"$ref":"#/components/schemas/InvoiceStatus"},"type":{"$ref":"#/components/schemas/InvoiceType"},"date_from":{"type":"string","format":"date","description":"Issue date from (inclusive)."},"date_to":{"type":"string","format":"date","description":"Issue date to (inclusive)."},"customer_id":{"$ref":"#/components/schemas/UUID"},"recipient_name":{"type":"string","description":"Partial match on the recipient name."},"recipient_nif":{"type":"string","description":"Partial match on the recipient tax id."},"series_code":{"type":"string","description":"Exact series code."}}},"CreateInvoiceExportRequest":{"type":"object","description":"An export of the invoices of this company. The format travels in the body, not in the URL,\nso a new format never adds a route.\n","properties":{"format":{"type":"string","enum":["SUMMARY","ITEMS"],"default":"SUMMARY","description":"- **SUMMARY**: one row per invoice with aggregated totals.\n- **ITEMS**: one row per invoice line.\n\nIgnored when `formats` is present.\n"},"formats":{"type":"array","description":"Several formats in ONE file: the workbook carries one sheet per format, in the\norder given (a repeated format yields a single sheet), followed at the end by a\ndisbursements sheet (named `Suplidos`, described in the operation) whenever any\nexported invoice carries disbursement lines. Takes precedence over `format`;\nempty or absent, `format` applies.\n","items":{"$ref":"#/components/schemas/InvoiceExportFormat"},"example":["SUMMARY","ITEMS"]},"invoice_ids":{"type":"array","description":"Specific invoices to export. When present, `filters` is ignored.","items":{"$ref":"#/components/schemas/UUID"}},"filters":{"$ref":"#/components/schemas/InvoiceExportFilters"}}},"VeriFactuRecordOperation":{"type":"string","description":"Operation a VeriFactu record submits to the AEAT.\n\n* `REGISTRATION` — registration of the invoice.\n* `VOID` — cancellation of a previously registered invoice.\n","enum":["REGISTRATION","VOID"],"example":"REGISTRATION"},"VeriFactuRecord":{"type":"object","description":"One VeriFactu record of an invoice: its registration or, if the invoice was voided, its\ncancellation. The invoice's `verifactu` block summarises the\nregistration; this resource lists each record with its own status.\n","required":["id","operation"],"properties":{"id":{"type":"string","format":"uuid","description":"Record ID. Same value as `verifactu_registration_id` in the payload of the\n`verifactu.status.updated` webhook event.\n","example":"7c9e6679-7425-40de-944b-e07fc1f90ae7"},"operation":{"$ref":"#/components/schemas/VeriFactuRecordOperation"},"submission_status":{"allOf":[{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"}],"description":"Submission status of this record, in the same vocabulary as the invoice's\n`verifactu.submission_status`. For `operation` `VOID` the final successful state is\n`VOIDED`, not `ACCEPTED`; a rejected cancellation is `REJECTED`. On `REJECTED`,\n`error_message` gives the reason and `error_code` carries the AEAT's code when the AEAT\ngave one, or BeeL.'s code when the submission was rejected before reaching the AEAT for\na reason BeeL. recognises.\n"},"invoice_hash":{"type":"string","description":"Invoice SHA-256 hash according to VeriFactu regulations. Only on `REGISTRATION` records.","example":"3A5B7C9D1E2F3A4B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5E6F7A8B"},"registration_number":{"type":"string","description":"Identifier (UUID) of this record in the VeriFactu submission, assigned when it is\nsubmitted. It is not an AEAT code: quote it when you ask BeeL about the record.\n","example":"4f8c2a1e-9b3d-4e7a-8c5f-1d2e3f4a5b6c"},"registered_at":{"type":"string","format":"date-time","description":"Date and time this record was created. A record rejected before reaching the AEAT\nalso has one.\n"},"qr_url":{"type":"string","description":"AEAT verification URL encoded in the invoice QR code. Only on `REGISTRATION` records.\n","example":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2025%2F0042&fecha=20-01-2025&importe=1590.00"},"error_code":{"$ref":"#/components/schemas/VeriFactuErrorCode"},"error_message":{"type":"string","nullable":true,"description":"Human-readable reason for the outcome. When the AEAT reported a remark or an error on the\nrecord, it is the AEAT's own description. When BeeL. decided the outcome (the submission\nwas rejected before reaching the AEAT, or BeeL. stopped waiting for a final answer), it\nis a message written by BeeL., in the language of the request.\n","example":"Factura ya existe en el sistema"}}},"InvoicePreviewResponse":{"type":"object","required":["success","data","meta"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["image_url","expires_in_seconds"],"properties":{"image_url":{"type":"string","format":"uri","description":"Pre-signed URL to the invoice preview image (WebP), for inline rendering (valid for 5 minutes)","example":"https://storage.example.com/beel-invoices/invoices/user-uuid/factura-uuid_preview.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=..."},"expires_in_seconds":{"type":"integer","description":"Seconds until the URL expires","example":300,"minimum":1}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"SetInvoiceStatusRequest":{"type":"object","description":"Replaces the status of an invoice. The valid transitions are the same ones the dedicated\nverbs used to expose, and the domain still rejects any transition that is not allowed.\n","required":["status"],"properties":{"status":{"type":"string","enum":["ISSUED","SENT","PAID"],"description":"Target status.\n\n- **PAID**: from `ISSUED`, `SENT` or `OVERDUE`.\n- **SENT**: from `ISSUED`. Records `sent_at`.\n- **ISSUED**: from `SENT` only. Clears `sent_at`. Use it to undo a `SENT` set by\n  mistake; it never un-issues an invoice, which is irreversible.\n"},"payment_date":{"type":"string","format":"date","description":"Payment date. Only read when `status` is `PAID`; defaults to today.","example":"2025-01-15"},"payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentInfo"}],"description":"Payment details object. Only read when `status` is `PAID`. Example:\n`{ \"method\": \"BANK_TRANSFER\", \"iban\": \"ES9121000418450200051332\" }`\n"},"sent_at":{"type":"string","format":"date-time","description":"Timestamp for when the invoice was sent. Only read when `status` is `SENT`;\ndefaults to now.\n","example":"2025-01-29T18:45:00Z"}}},"InvoiceSchedule":{"type":"object","description":"The scheduling of an invoice, as a singleton sub-resource.","required":["scheduled_for","generation_mode"],"properties":{"scheduled_for":{"type":"string","format":"date","description":"Date on which the invoice will be processed.","example":"2025-02-15"},"generation_mode":{"$ref":"#/components/schemas/GenerationAction"}}},"InvoiceScheduleResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/InvoiceSchedule"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"SetInvoiceScheduleRequest":{"type":"object","description":"Full replacement of the scheduling of an invoice (RFC 9110 §9.3.4). Both fields are\nrequired on purpose: `generation_mode` has no default, because silently falling back to\n`DRAFT` would downgrade an `ISSUE_AND_SEND` and the invoice would never be issued.\n","required":["scheduled_for","generation_mode"],"properties":{"scheduled_for":{"type":"string","format":"date","description":"Date on which the invoice should be processed. Today or later.","example":"2025-02-15"},"generation_mode":{"$ref":"#/components/schemas/GenerationAction"}}},"PatchInvoiceScheduleRequest":{"type":"object","description":"Partial update of the scheduling of an invoice that is **already** scheduled — same\nconvention as `PATCH …/{invoice_id}` on the invoice itself (RFC 9110 §9.3.4 PATCH):\nonly the fields present in the body are applied, the rest keep their current value.\n\nUnlike the `PUT`, this never creates a scheduling: it is a move, not a replace. At\nleast one of the two fields is required — an empty body is rejected with `422`.\n","minProperties":1,"properties":{"scheduled_for":{"type":"string","format":"date","description":"New date on which the invoice should be processed. Today or later; an earlier date\nis rejected with `422 SCHEDULED_DATE_IN_PAST`. If omitted, the currently scheduled\n`generation_mode` is kept.\n","example":"2025-02-15"},"generation_mode":{"allOf":[{"$ref":"#/components/schemas/GenerationAction"}],"description":"New generation mode. If omitted, the currently scheduled `scheduled_for` is kept.\n"}}},"RecurringInvoiceStatus":{"type":"string","description":"Lifecycle state of a recurring invoice schedule.\n\n- `ACTIVE`: invoices are generated on schedule.\n- `PAUSED`: automatic generation is stopped and the schedule is kept; `pause` says who\n  stopped it. It can be resumed.\n- `COMPLETED`: the schedule has ended for good and generates nothing more; `completion`\n  says why. Terminal: it cannot be resumed or paused.\n","enum":["ACTIVE","PAUSED","COMPLETED"],"example":"ACTIVE"},"RecurringInvoiceSortBy":{"type":"string","enum":["name","next_generation","status","created_at"],"description":"Field to sort by: `name` the template name, `next_generation` the next scheduled\ngeneration date, `status` the lifecycle state, `created_at` the creation instant.\n\n`next_generation` orders by the schedule stored on the template, which is not always the\ndate the response publishes: a template that carries `null` — `COMPLETED`, or `PAUSED` with\na date already behind — is still ordered by the date it keeps. The ordering is stable and\nthe same for two requests of the same page; it does not depend on today's date.\n"},"RecurringInvoiceSortOrder":{"type":"string","enum":["asc","desc"]},"RecurringInvoicePauseReason":{"type":"string","description":"Who stopped the schedule. `USER` a person did, from the API or the dashboard; `DOWNGRADE` the\naccount lost the recurring-invoices feature; `GENERATION_FAILURE` an unattended run failed with\nsomething waiting will not fix — that one carries a `blocker`.\n","enum":["USER","DOWNGRADE","GENERATION_FAILURE"],"example":"GENERATION_FAILURE"},"RecurringInvoicePause":{"type":"object","nullable":true,"description":"Why this schedule is paused. Present while `status` is `PAUSED`; absent otherwise, and also\nabsent for schedules paused before this was recorded.\n\n`PAUSED` covers three facts that are not interchangeable: someone paused it, a plan downgrade\npaused it, or an unattended generation failed with something waiting will not fix. A transient\nfailure (network, timeout, tax authority downtime) does NOT pause the schedule — the next run\nretries it.\n","properties":{"reason":{"$ref":"#/components/schemas/RecurringInvoicePauseReason"},"since":{"type":"string","format":"date-time"},"blocker":{"type":"string","description":"What blocked the emission, using the same codes the emission error returns (the `blockers[]`\nof a 422 `EMISSION_NOT_READY`). Only for `GENERATION_FAILURE`, and it may be absent when the\npermanent failure was not a readiness one. Resuming is rejected while this blocker is still\nin effect.\n","example":"NIF_NOT_REGISTERED"}}},"RecurringInvoiceCompletion":{"type":"object","nullable":true,"description":"Why this schedule ended. Present while `status` is `COMPLETED`; absent otherwise, and also\nabsent for schedules completed before this was recorded — that cause is not known and is not\ninvented.\n\n`COMPLETED` covers facts that are not interchangeable: the owner ended it, the calendar ran\nout, or the agreed number of invoices did. It stays a **single** terminal status and `status` keeps its three values: no business rule\nbranches on the cause, and a new status value would break every exhaustive `switch`. New causes\nland in `reason`, so treat it as an open set.\n","properties":{"reason":{"type":"string","enum":["USER","END_DATE_REACHED","MAX_INVOICES_REACHED"],"description":"`USER` — someone completed it (`PUT .../status` to `COMPLETED`).\n`END_DATE_REACHED` — the next occurrence landed past `end_date`, so there was nothing left\nto generate. This includes automatic generation and the common close of generating, skipping\nand resuming.\n`MAX_INVOICES_REACHED` — the `max_invoices` cap ran out: the last agreed invoice was\ngenerated (or the cap was moved down to the number already generated). When a template\nexhausts its cap and its `end_date` on the very same turn this is the cause reported: the\ncap is the fact that just happened, the date is the calendar consequence that follows.\n"},"at":{"type":"string","format":"date-time","description":"Instant the schedule was closed."}}},"PricingMode":{"type":"string","description":"How the line amount was entered.\n\n- `UNIT_PRICE`: classic mode — the amount is derived from `unit_price`\n  (`quantity × unit_price × (1 − discount / 100)`).\n- `TOTAL_EXCLUDING_TAX`: total-declared mode — `total_excluding_tax` is the exact\n  taxable base and `unit_price` is derived and informational\n  (`total / quantity`, 4 decimals).\n- `TOTAL_INCLUDING_TAX`: tax-inclusive total-declared mode —\n  `total_including_tax` is what the customer paid (taxable base + VAT +\n  equivalence surcharge) and the engine works the breakdown backwards so the\n  rounded amounts add up to the declared total exactly.\n\nIn both total-declared modes re-multiplying the derived `unit_price` does NOT\nreproduce the declared total on large quantities (1.00 € ÷ 300 units → 0.0033 ×\n300 = 0.99), which is precisely why the declared total is the source of truth.\n","enum":["UNIT_PRICE","TOTAL_EXCLUDING_TAX","TOTAL_INCLUDING_TAX"],"example":"UNIT_PRICE"},"PatchRecurringInvoiceRequest":{"type":"object","description":"Partial update of a recurring invoice (RFC 5789). Only the fields present in the\nbody are touched:\n\n- **field omitted** → the current value is kept;\n- **field sent with a value** → replaced;\n- **field sent as `null`** → cleared (only where documented as nullable below).\n\nIn particular, changing the lines no longer wipes the recipient, and the schedule\n(`day_of_month`, `start_date`) stays put unless you send it.\n","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S.*$","description":"Template name. Cannot be cleared."},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, the current one is kept — it is\nnever reset to `MONTHLY`.\n\n**Changing it reschedules the template.** The next generation is recalculated on the new\ngrid — the `day_of_month` dates anchored at `start_date`, one every cadence — so it can\nland months later than the one you saw before the call. **If the template has already\nissued invoices, the landing respects that**: it is never a period that was already\nbilled, and never before today either — the first point of the new grid strictly after\nthe last invoiced one. It also **discards a pending skip**: the skipped period only\nexisted as a point of the old grid, and that grid is gone. Send the same cadence and\nnothing is rescheduled.\n"},"day_of_month":{"type":"integer","minimum":1,"maximum":31,"description":"Day of the month the invoices are issued. Moves the next generation immediately, on an\n`ACTIVE` template and on a `PAUSED` one alike: the response to this very call already\ncarries the new `next_generation`. On a paused template the new day rules from the edit,\nnot from the day you resume — resuming keeps the date this call left, so editing the day\nof a paused schedule is never ignored for a round.\n\nA month that does not have that day falls back to its last day: a template on `31`\nissues on 28 February (29 in a leap year) and on 30 April. So `31` is how you ask for\nthe last day of the month it generates in — there is no separate flag for it, and the\naccepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date","description":"First issue date. Only **changeable** while the template has not generated any invoice\nyet: once it has issued, a *different* date is rejected with a 422\n`RECURRING_START_DATE_NOT_EDITABLE`. Sending the value it already has is a no-op and\nsucceeds, so a read-modify-write cycle never has to strip the field out of the body.\n\nA past date is accepted and stored as sent, but it never anchors generation in the\npast: `next_generation` becomes the first date of the template's own calendar on or\nafter today (today included) — the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency` — which on a quarterly or yearly template can be\nmonths from now. Creating and updating apply the same rule. If today's period was\nalready invoiced in the system you are migrating from, send a `start_date` of tomorrow\n(or of the next period) so it is not invoiced twice.\n"},"draft_in_advance":{"type":"boolean","description":"Whether the template prepares a draft for review before emitting. The window is fixed at\n5 days and both options emit on the scheduled day. Generating manually a period whose\nscheduled date is today or earlier emits it on the spot. Omitted, the current value is\nkept.\n"},"preview_days":{"type":"integer","minimum":0,"maximum":30,"deprecated":true,"description":"**Deprecated.** Superseded by `draft_in_advance`. Any value greater than `0` means the\nsame as `draft_in_advance: true`, and `0` the same as `false`. When both are sent,\n`draft_in_advance` wins. It will be removed in a future version.\n"},"end_date":{"type":"string","format":"date","nullable":true,"description":"Date the recurrence stops. Send `null` to make it open-ended."},"max_invoices":{"type":"integer","nullable":true,"example":6,"description":"Total number of invoices this template will generate before ending on its own, between\n2 and 600. Send `null` to stop capping the recurrence by number.\n\nEditing it MOVES THE GOAL, it does not add turns: above the invoices already generated the\ntemplate stays active; exactly equal ends it in this very call (with\n`completion.reason = MAX_INVOICES_REACHED`); below it the call is rejected — issued invoices\nare fiscal facts, and someone typing a lower number is asking to stop now, which is what\nending the recurrence is for.\n\nA recurrence ends in ONE way, and the conflict is judged on the RESULTING state, not on the\nbody: sending `max_invoices` to a template that already has an `end_date` is rejected with\n`RECURRING_END_MODE_CONFLICT` even though the body only mentions one of them. Switching mode\nis a single call that says both things — the new field with a value and the old one as\n`null`. Nothing is cleared silently.\n"},"series_id":{"type":"string","format":"uuid","description":"Series the generated invoices are numbered in. Cannot be cleared."},"invoice_type":{"allOf":[{"$ref":"#/components/schemas/RecurringInvoiceType"}],"description":"Type of the invoices the template generates. Omitted, the current type is kept; it cannot be cleared.\nA change is judged on the resulting template, with the rules of the new type: its series\nmust accept it (`SERIES_INCOMPATIBLE_DOC_TYPE` otherwise), a `SIMPLIFIED` template\ncannot keep an identified recipient (`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`)\nnor lines a simplified invoice does not admit. Send the new `series_id`, and\n`customer_id: null` when moving to `SIMPLIFIED`, in the same call.\n"},"customer_id":{"type":"string","format":"uuid","nullable":true,"description":"Recipient of the generated invoices. Send `null` to leave the template without\na recipient; omit it to keep the current one.\n"},"lines":{"type":"array","minItems":1,"nullable":true,"items":{"$ref":"#/components/schemas/RecurringLineRequest"},"description":"Template lines, replaced as a whole (they are not patched line by line). Omit\nthem to keep the current ones — a template with no lines invoices nothing, so\nan empty array is rejected.\n"},"payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentMethod"}],"nullable":true,"description":"Payment method. Replaced as a whole together with `payment_iban`,\n`payment_swift` and `payment_term_days`: send them in the same request or they\nare dropped. Send `null` to state that no payment method applies.\n\n`payment_iban`, `payment_swift` and `payment_term_days` are detail of this\nmethod, not standalone fields: sending any of them without a `payment_method`\nthat is present, non-null and different from `NONE` is rejected with `422`\n(`PAYMENT_DETAILS_REQUIRE_METHOD`).\n"},"payment_iban":{"type":"string","nullable":true},"payment_swift":{"type":"string","nullable":true},"payment_term_days":{"type":"integer","nullable":true},"notes":{"type":"string","nullable":true,"description":"Notes printed on the generated invoices. Send `null` to clear them."},"send_automatically":{"type":"boolean"},"email_configuration":{"allOf":[{"$ref":"#/components/schemas/RecurringEmailConfigRequest"}],"nullable":true,"description":"Email delivery settings, replaced as a whole. Send `null` to stop sending the\ngenerated invoices by email.\n"}}},"NextOccurrence":{"description":"The invoice a recurring template would produce on its next generation, computed\nfrom the current issuer, recipient and series data. Nothing is persisted and no\nnumbering is consumed, so it carries neither `id` nor `created_at`/`updated_at`,\nand `invoice_number` and `number` travel as `null` — the number is assigned when\nthe invoice is actually generated. Every other field is the one the generated\ninvoice would carry.\n","allOf":[{"$ref":"#/components/schemas/InvoiceBase"},{"type":"object","properties":{"invoice_number":{"type":"string","nullable":true,"description":"Always `null` here: the number is drawn from the series when the invoice is\nactually generated, and a preview draws nothing.\n"},"number":{"type":"integer","nullable":true,"description":"Always `null` here, for the same reason as `invoice_number`.\n"}}}]},"RecurringInvoiceHistoryEntryType":{"type":"string","description":"What happened in one scheduled slot of the schedule.\n\nOnly `GENERATED` consumes the slot and produces an invoice. `FAILED`, `SKIPPED` and `PAUSED` are\nrecorded to explain why a period has no invoice, and the slot stays free: the next run may still\nfill it.\n","enum":["GENERATED","FAILED","SKIPPED","PAUSED"],"example":"GENERATED"},"RecurringInvoiceHistoryEntryOrigin":{"type":"string","enum":["MANUAL","UNATTENDED"],"example":"UNATTENDED"},"RecurringInvoiceStatsBlock":{"type":"object","description":"How many schedules fall in this block and what they are worth per month.\n","required":["count","monthly_amount","uncounted_count"],"properties":{"count":{"type":"integer","format":"int32","minimum":0,"example":7,"description":"Schedules in this block, priced or not. It counts schedules, not money, so a schedule\nwhose amount could not be computed is still counted here.\n"},"monthly_amount":{"type":"number","example":2140,"description":"What the schedules in this block bring in per month. Each schedule contributes the\nper-schedule number the list publishes as `amount` (what its next invoice will charge),\ndivided by the months its `frequency` covers: `MONTHLY` as is, `QUARTERLY` divided by 3,\n`YEARLY` divided by 12. The sum is rounded to the cent once, at the end. When every\nschedule is `MONTHLY`, adding the `amount` of the rows by hand gives exactly this figure.\n\nIt covers `count − uncounted_count` schedules: the ones that could not be priced are\n**not** added as zero, they are left out and counted in `uncounted_count`.\n\nIt is not an MRR: it carries taxes and withholding exactly as the invoices will.\n\nAlways a number, never `null` — with no schedules at all it is `0`.\n"},"uncounted_count":{"type":"integer","format":"int32","minimum":0,"example":0,"description":"Schedules of this block whose lines the engine could not price — the same ones the list\nreturns with `amount: null` (no lines, or lines the engine rejects, including a VAT rate\nthat is no longer accepted on the date of the next invoice). They are excluded\nfrom `monthly_amount` and counted here, so a partial total never passes for a complete\none. `0` means the total covers every schedule in the block.\n"}}},"RecurringInvoiceStats":{"type":"object","description":"Live recurring schedules of this company and the money behind them, in two blocks that\nare **never added together**.\n\n`active` is a forecast: what is going to be invoiced if nothing breaks. `stopped` is the\nopposite — invoicing that should be happening and is not, because the unattended\ngeneration failed. They answer different questions, so there is no grand total here and\nyou should not compute one.\n\nSchedules **a person paused**, and those parked by a plan downgrade, are in **neither**\nblock: they are not a forecast (they will not generate) and not a breakage to fix\n(someone decided they should not invoice). Completed schedules are in neither block\neither.\n","required":["active","stopped"],"properties":{"active":{"$ref":"#/components/schemas/RecurringInvoiceStatsBlock"},"stopped":{"$ref":"#/components/schemas/RecurringInvoiceStatsBlock"}}},"CreateRecurringInvoiceDerivationRequest":{"type":"object","description":"Derives a recurring invoice template from an existing invoice: lines, recipient, series and\npayment data are taken from it, so only the recurrence is described here. The source invoice\nis not modified.\n\nDeliberately not a variant of `CreateRecurringInvoiceRequest`: that one requires `series_id`,\n`invoice_type` and `lines`, which this call does not carry. Relaxing them there would let a\ntemplate be created from scratch with no lines at all.\n","required":["from_invoice_id","name","day_of_month","start_date"],"properties":{"from_invoice_id":{"type":"string","format":"uuid","description":"Invoice the template is derived from. It must belong to the company in the path; an\ninvoice you cannot reach is reported the same way as one that does not exist.\n"},"name":{"type":"string","minLength":1,"maxLength":255,"pattern":"^\\S.*$"},"frequency":{"type":"string","enum":["MONTHLY","QUARTERLY","YEARLY"],"description":"Generation cadence: `MONTHLY` every month, `QUARTERLY` every 3 months, `YEARLY` every 12 months. Omitted, `MONTHLY` applies.\n\nIt governs the step from the first invoice onwards, not where that first one lands: the\nfirst occurrence is the first `day_of_month` on or after `start_date`, found one month at\na time whatever the cadence. A yearly template starting 15 February with `day_of_month`\n10 first invoices on 10 March, then every 10 March after that — it does not wait a year.\n"},"day_of_month":{"type":"integer","minimum":1,"maximum":31,"description":"Day of the month the invoices are issued. A month that does not have that day falls\nback to its last day: a template on `31` issues on 28 February (29 in a leap year)\nand on 30 April. So `31` is how you ask for the last day of the month it generates\nin — there is no separate flag for it, and the accepted range stays 1–31.\n\nThe adjustment does not stick and the schedule does not drift: every generation is\nrecalculated from the `day_of_month` you sent, never from the date it was adjusted\nto (31 Jan → 28 Feb → 31 Mar).\n"},"start_date":{"type":"string","format":"date","description":"Date the subscription started. A past date is accepted and stored as sent — useful\nwhen migrating subscriptions from another system — but it never anchors generation\nin the past. `next_generation` becomes the first date of the template's own calendar\non or after today (today included): the grid of `day_of_month` dates anchored at\n`start_date`, one every `frequency`. On a quarterly or yearly template that can be\nmonths from now, not this month. Invoices are never back-dated, so the missed periods\nare not generated. If today's period was already invoiced in the system you are\nmigrating from, send a `start_date` of tomorrow (or of the next period) so it is not\ninvoiced twice.\n"},"draft_in_advance":{"type":"boolean","description":"Whether the template prepares a draft for review before emitting. The window is fixed at\n5 days and both options emit on the scheduled day. Omitted, the template is created\nwithout the review draft. Same field, same meaning and same default as when you create a\ntemplate from scratch: deriving from an invoice does not give it a second semantics.\n"},"end_date":{"type":"string","format":"date","nullable":true},"send_automatically":{"type":"boolean","default":false},"email_configuration":{"$ref":"#/components/schemas/RecurringEmailConfigRequest"}}},"CustomerSortBy":{"type":"string","description":"Customer field the list is ordered by. Ties are broken by customer id, so pages are\nstable.\n\n- `legal_name`: legal name.\n- `nif`: tax identifier.\n- `email`: email address.\n- `phone`: phone number.\n- `city`: city of the address.\n- `province`: province of the address.\n- `active`: whether the customer is active.\n- `created_at`: creation instant.\n","enum":["legal_name","nif","email","phone","city","province","active","created_at"],"example":"legal_name"},"PatchCustomerRequest":{"type":"object","description":"Partial update of a customer (RFC 5789). Only the fields present in the\nbody are touched:\n\n- **field omitted** → the current value is kept;\n- **field sent with a value** → replaced;\n- **field sent as `null`** → cleared (only where documented as nullable\n  below).\n\nThe resulting customer goes through the same validation as `PUT`\n(AEAT census check for Spanish NIFs, duplicate identifier check\nexcluding this customer, field formats). The census check matches\n`legal_name` only for individuals; for a company the name is\n**not verified** and only the CIF decides.\n","properties":{"nif":{"allOf":[{"$ref":"#/components/schemas/NIF"}],"description":"New Spanish Tax ID. Mutually exclusive with `alternative_id`.\nOmit to keep the current identifier; it cannot be cleared.\n"},"alternative_id":{"allOf":[{"$ref":"#/components/schemas/AlternativeIdentifier"}],"description":"New alternative identifier (non-Spanish customers). Mutually\nexclusive with `nif`. Omit to keep the current identifier.\n"},"legal_name":{"type":"string","minLength":1,"maxLength":120,"description":"Customer legal name. Cannot be cleared. Matched against the AEAT census only\nfor individuals; for a company the name is **not verified**.\n"},"trade_name":{"type":"string","maxLength":120,"nullable":true,"description":"Customer trade name. Send `null` to clear it."},"address":{"allOf":[{"$ref":"#/components/schemas/Address"}],"description":"Full address. Replaced as a whole (the address itself is not\npatched field by field) and cannot be cleared.\n"},"phone":{"allOf":[{"$ref":"#/components/schemas/PhoneInput"}],"nullable":true,"description":"Phone number. Send `null` to clear it."},"email":{"type":"string","format":"email","minLength":5,"maxLength":255,"nullable":true,"description":"Main contact address. Invoice emails go here only when the customer has no\n`billing_emails`. Send `null` to clear it.\n"},"website":{"type":"string","maxLength":255,"nullable":true,"pattern":"^(https?://.+|)$","description":"Website URL. Send `null` to clear it."},"billing_emails":{"type":"array","nullable":true,"items":{"$ref":"#/components/schemas/Email"},"description":"Addresses that receive the customer's invoice emails. When the send request names no\n`recipients` and the invoice has no `email_config` recipients, invoices go to these\naddresses, and not to `email`; `email` is used only when `billing_emails` is empty.\nReplaced as a whole; send `null` or `[]` to remove them all.\n"},"contact_person":{"type":"string","maxLength":200,"nullable":true,"description":"Contact person name. Send `null` to clear it."},"notes":{"type":"string","nullable":true,"description":"Additional notes. Send `null` to clear it."},"preferred_payment_method":{"allOf":[{"$ref":"#/components/schemas/PaymentInfo"}],"description":"Default payment method. Replaced as a whole; omit it to keep the\ncurrent one.\n"},"general_discount":{"type":"number","minimum":0,"maximum":100,"nullable":true,"description":"General discount percentage. Send `null` to clear it."},"active":{"type":"boolean","description":"Whether the customer is active or inactive."}}},"CustomerEcho":{"type":"object","required":["legal_name"],"description":"A customer as it was submitted, once parsed and validated. Same fields as `Customer` minus\nthe ones only a stored customer has (`id`, `created_at`, `updated_at`): a rehearsal writes\nnothing, so it has none of them to report. The identifier of a customer that WAS created is\nreported next to this object, in `customer_id`.\n\n`Customer` itself is untouched — many endpoints return it and it does require an `id`.\n","properties":{"legal_name":{"type":"string","example":"Empresa Cliente SL"},"trade_name":{"type":"string","example":"EmpresaCliente"},"nif":{"$ref":"#/components/schemas/NIF"},"alternative_id":{"$ref":"#/components/schemas/AlternativeIdentifier"},"address":{"$ref":"#/components/schemas/Address"},"phone":{"$ref":"#/components/schemas/Phone"},"email":{"type":"string","format":"email","minLength":5,"maxLength":255,"nullable":true,"description":"Main contact address (minimum valid email is 5 chars, e.g. a@b.co). Invoice emails go\nhere only when the customer has no `billing_emails`.\n"},"website":{"type":"string","maxLength":255,"nullable":true,"description":"Website URL","pattern":"^(https?://.+|)$"},"billing_emails":{"type":"array","items":{"$ref":"#/components/schemas/Email"},"description":"Addresses that receive the customer's invoice emails. When the send request names no\n`recipients` and the invoice has no `email_config` recipients, invoices go to these\naddresses, and not to `email`; `email` is used only when `billing_emails` is empty.\n"},"contact_person":{"type":"string","example":"María García"},"notes":{"type":"string","maxLength":1000},"preferred_payment_method":{"$ref":"#/components/schemas/PaymentInfo"},"general_discount":{"type":"number","minimum":0,"maximum":100},"active":{"type":"boolean"}}},"CustomerBulkDeleteMetadata":{"type":"object","required":["total_customers","operation","is_dry_run"],"description":"What the operation was, and whether it was a rehearsal.","properties":{"total_customers":{"type":"integer","description":"Number of identifiers submitted.","example":10},"operation":{"type":"string","enum":["DELETE_CUSTOMERS"],"description":"The batch operation this report belongs to.","example":"DELETE_CUSTOMERS"},"is_dry_run":{"type":"boolean","description":"Always `false`. Deleting has no rehearsal mode; the field is here so every batch report\nof this API has the same shape.\n","example":false}}},"CustomerBulkDeleteError":{"type":"object","required":["code","message"],"description":"Why a row was not deleted.","properties":{"code":{"type":"string","description":"Stable, uppercase, never translated — this is what an integration compares.\n`NOT_FOUND` means the id does not resolve to a customer of the company, and\n`INTERNAL_ERROR` that the row failed for an unexpected reason and was not deleted.\n`CLIENT_HAS_INVOICES` is deliberately the same code the single `DELETE` answers with\n`409`, so the single and the bulk route never disagree about the same customer.\n`CLIENT_HAS_RECURRING_INVOICE`: an active or paused recurring invoice uses the\ncustomer as its recipient. The single `DELETE` rejects the same case with `400`\n`REFERENCED_BY_RECURRING_INVOICE`.\n","enum":["NOT_FOUND","CLIENT_HAS_INVOICES","CLIENT_HAS_RECURRING_INVOICE","INTERNAL_ERROR"],"example":"CLIENT_HAS_INVOICES"},"message":{"type":"string","description":"Human-readable text, already resolved to the language of the request.","example":"A customer with invoices cannot be deleted: its tax record is kept intact. Deactivate it (active: false) to stop using it."}}},"CustomerBulkDeleteItem":{"type":"object","required":["index","customer_id","status"],"description":"How one submitted identifier ended up.","properties":{"index":{"type":"integer","description":"Position of the identifier in the submitted list (0-based).","example":0},"customer_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"The identifier that was submitted."},"status":{"type":"string","enum":["DELETED","NOT_FOUND","HAS_INVOICES","HAS_RECURRING_INVOICE","ERROR"],"description":"Outcome of this row, in the vocabulary of deleting (deleting validates nothing\nbeforehand, so it does not speak of `VALID`):\n- `DELETED`: deleted; its identifier is free to be used again\n- `NOT_FOUND`: no such customer, or the current company/grant cannot see it\n- `HAS_INVOICES`: it has invoices, so it cannot be deleted — **deactivate it instead**\n  (`PATCH` with `active: false`). This is a business rule, not a failure: retrying the\n  delete will never work\n- `HAS_RECURRING_INVOICE`: an active or paused recurring invoice template uses it as its\n  customer, so it cannot be deleted — finish the template or change its customer first.\n  This is a business rule, not a failure: retrying the delete will never work\n- `ERROR`: an unexpected failure while processing this row\n","example":"DELETED"},"error":{"allOf":[{"$ref":"#/components/schemas/CustomerBulkDeleteError"}],"description":"Present only when the customer was not deleted."}}},"CustomerBulkDeleteStatistics":{"type":"object","required":["total_processed","deleted","not_found","has_invoices","has_recurring_invoice","errors"],"description":"Counters derived by counting the entries of `customers_deletion`, never accumulated while\nprocessing. Each row falls into exactly one of `deleted`, `not_found`, `has_invoices`,\n`has_recurring_invoice` and `errors`, so those five add up to `total_processed`.\n","properties":{"total_processed":{"type":"integer","description":"Identifiers submitted.","example":10},"deleted":{"type":"integer","description":"Customers deleted (their identifiers are free again).","example":7},"not_found":{"type":"integer","description":"Identifiers that do not exist, or that the current company/grant cannot see.","example":1},"has_invoices":{"type":"integer","description":"Customers that have invoices and therefore cannot be deleted. Not a failure: deactivate\nthem instead.\n","example":2},"has_recurring_invoice":{"type":"integer","description":"Customers with an active or paused recurring invoice template using them as its customer,\nand therefore cannot be deleted. Not a failure: finish the template or change its\ncustomer instead.\n","example":0},"errors":{"type":"integer","description":"Rows that failed for an unexpected reason.","example":0}}},"CustomerBulkDeleteLegacyError":{"type":"object","deprecated":true,"required":["customer_id","error"],"description":"**Deprecated.** Legacy shape of a row that was not deleted.","properties":{"customer_id":{"$ref":"#/components/schemas/UUID"},"error":{"$ref":"#/components/schemas/CustomerBulkDeleteError"}}},"CustomerBulkDeleteResult":{"type":"object","required":["metadata","customers_deletion","statistics","deleted_ids","total","successful","failed","deactivated_ids"],"description":"Report of a bulk delete of customers, in the same shape every batch operation of this API\nuses (ADR-0011): `metadata` (what the operation was), **one entry per identifier submitted**\nin its original position and never filtered, and `statistics` derived by counting those\nentries.\n\n**This endpoint deletes, it does not deactivate.** A deleted customer is retained internally\nfor tax record-keeping but is no longer exposed by the API, and **its identifier is released\nfor reuse** (the unique indexes only cover rows that are not deleted). Deactivating is the\nopposite: the customer stays where it is, marked inactive, with its NIF still taken. A\ncustomer that has invoices can never be deleted — it can only be deactivated — which is why\nit gets its own row status (`HAS_INVOICES`) instead of being reported as a failure.\n\n**HTTP status:** always `200` when the request was processed, even if no row could be\ndeleted. The code says whether the request was processed, not whether every row went well;\nthe split is in this report.\n","properties":{"metadata":{"$ref":"#/components/schemas/CustomerBulkDeleteMetadata"},"customers_deletion":{"type":"array","description":"One entry per identifier submitted, in the order they were sent and never filtered: a\ncustomer that was not deleted keeps its place and its `index`, so the response lines up\nwith the request.\n","items":{"$ref":"#/components/schemas/CustomerBulkDeleteItem"}},"statistics":{"$ref":"#/components/schemas/CustomerBulkDeleteStatistics"},"deleted_ids":{"type":"array","description":"IDs of the customers that were deleted.","items":{"$ref":"#/components/schemas/UUID"}},"deactivated_ids":{"type":"array","deprecated":true,"description":"**Deprecated alias of `deleted_ids`** — identical content. The name was wrong: this\noperation deletes (freeing the identifier), it does not deactivate (which keeps it\ntaken). Read `deleted_ids`. It will be removed no earlier than the `Sunset` date\nannounced for the deprecated flat routes.\n","items":{"$ref":"#/components/schemas/UUID"}},"total":{"type":"integer","deprecated":true,"description":"**Deprecated.** Same as `statistics.total_processed`.","example":10},"successful":{"type":"integer","deprecated":true,"description":"**Deprecated.** Same as `statistics.deleted`.","example":8},"failed":{"type":"integer","deprecated":true,"description":"**Deprecated.** Number of submitted identifiers that were NOT deleted, for whatever\nreason. It lumps together a customer that does not exist and one that has invoices and\nnever can be deleted — which is exactly why the per-row `status` exists. Read\n`statistics`.\n","example":2},"errors":{"type":"array","deprecated":true,"description":"**Deprecated.** Projection of the entries of `customers_deletion` that were not deleted,\nin the same order. Read `customers_deletion`, which also reports the rows that worked.\n","items":{"$ref":"#/components/schemas/CustomerBulkDeleteLegacyError"}}}},"CustomerImportSource":{"type":"string","description":"Format of the file being imported. It is required and has no default: a new origin must be\ndeclared explicitly, so adding one never changes the meaning of an existing request.\n","enum":["csv","holded"]},"CsvImportErrorDetails":{"type":"object","description":"Machine-readable context for an import file the operation rejected as a whole. Every field\nis optional: each `error.code` fills the pair that applies to it and leaves the rest absent,\nso the value and the limit it exceeded can be compared without parsing `error.message`.\n","properties":{"file_size_mb":{"type":"number","format":"double","description":"Size of the uploaded file, in MB rounded to one decimal (`CSV_FILE_TOO_LARGE`)","example":7.3},"max_file_size_mb":{"type":"integer","description":"Largest file the operation accepts, in MB (`CSV_FILE_TOO_LARGE`)","example":5},"record_count":{"type":"integer","description":"Data rows read from the file, header row excluded (`TOO_MANY_RECORDS`)","example":1001},"max_records":{"type":"integer","description":"Most data rows the operation accepts (`TOO_MANY_RECORDS`)","example":1000},"missing_headers":{"type":"array","description":"Every required column absent from the header row, not just the first one found missing\n(`MISSING_HEADERS`)\n","items":{"type":"string"},"example":["telefono","direccion_calle"]},"found_headers":{"type":"array","description":"Columns actually read from the header row, one entry per column (`MISSING_HEADERS`).\nComparing it against `missing_headers` names what to add to the file.\n","items":{"type":"string"},"example":["nombre_fiscal","nif","email"]}}},"ProductSortBy":{"type":"string","description":"Product field the list is ordered by.\n\n- `name`: product name.\n- `code`: product code.\n- `category`: product category.\n- `default_price`: default unit price.\n- `created_at`: creation instant.\n","enum":["name","code","category","default_price","created_at"],"example":"name"},"PatchProductRequest":{"type":"object","description":"Partial update of a product (RFC 5789). Only the fields present in the\nbody are touched:\n\n- **field omitted** → the current value is kept;\n- **field sent with a value** → replaced;\n- **field sent as `null`** → cleared (only where documented as nullable\n  below).\n\nThe resulting product goes through the same validation as `PUT`\n(duplicate code check excluding this product, field formats, tax ranges).\n","properties":{"code":{"type":"string","maxLength":50,"pattern":"^[a-zA-Z0-9_-]*$","nullable":true,"description":"Unique alphanumeric product code. Send `null` to clear it.","example":"SERV-001"},"name":{"type":"string","maxLength":255,"description":"Product/service name. Cannot be cleared.","example":"Technical consulting"},"description":{"type":"string","nullable":true,"description":"Detailed description. Send `null` to clear it.","example":"Specialized technical consulting services"},"category":{"allOf":[{"$ref":"#/components/schemas/ProductCategory"}],"description":"Product category. Omit to keep the current one."},"default_price":{"type":"number","minimum":0,"multipleOf":0.0001,"nullable":true,"description":"Suggested default price. Send `null` to clear it.","example":85.5},"unit":{"type":"string","maxLength":50,"nullable":true,"description":"Unit of measure. Send `null` to clear it.","example":"hours"},"main_tax":{"allOf":[{"$ref":"#/components/schemas/TaxInfo"}],"description":"Main tax. Replaced as a whole (it is not patched field by field);\nomit it to keep the current one. A product always carries a main\ntax, so it cannot be cleared.\n"},"equivalence_surcharge_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"nullable":true,"description":"Equivalence surcharge percentage. Send `null` to state that none\napplies (equivalent to `0`).\n\nThe merged result must be coherent with the regime key: if this\nPATCH does not send `main_tax.regime_key`, the stored regime is\nadjusted automatically (`18` when the merged surcharge is > 0,\n`01` when it is not). With an explicit `regime_key` in this PATCH,\nan incoherent combination is rejected with a 422\n(`SURCHARGE_REQUIRES_REGIME` / `REGIME_REQUIRES_SURCHARGE`).\n","example":5.2},"irpf_rate":{"type":"number","minimum":0,"maximum":100,"multipleOf":0.01,"nullable":true,"description":"IRPF withholding percentage. Send `null` to state that none\napplies (equivalent to `0`).\n\nThe rate must be one of `IrpfPercentage` (`INVALID_IRPF` otherwise) and one\nthe company can bear, the same check as an invoice line (see\n`WithholdingOptions` in the tax configuration): an entity gets the rates of\nindividuals rejected with `IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`, and an individual gets\n`9.5` rejected with `IRPF_RATE_ONLY_FOR_CORPORATE_ISSUER`.\n","example":15},"active":{"type":"boolean","description":"Indicates whether the product is active.","example":true}}},"ProductBulkCreateMetadata":{"type":"object","required":["total_products","operation","is_dry_run"],"description":"What the operation was, and whether it was a rehearsal.","properties":{"total_products":{"type":"integer","description":"Number of products submitted.","example":10},"operation":{"type":"string","enum":["CREATE_PRODUCTS"],"description":"The batch operation this report belongs to.","example":"CREATE_PRODUCTS"},"is_dry_run":{"type":"boolean","description":"Always `false`. Creating products has no rehearsal mode; the field is here so every\nbatch report of this API has the same shape.\n","example":false}}},"ProductBulkCreateError":{"type":"object","required":["code","message"],"description":"Why a row was not created.","properties":{"code":{"type":"string","enum":["PRODUCT_DUPLICATE","PRODUCT_INVALID"],"description":"Stable, uppercase, never translated — this is what an integration compares.\n`PRODUCT_DUPLICATE` is deliberately the same code the single `POST` answers with `409`,\nso the single and the bulk route never disagree about the same product.\n","example":"PRODUCT_DUPLICATE"},"message":{"type":"string","description":"Human-readable text, already resolved to the language of the request. It used to be the\nraw i18n key, which neither reads nor is stable against a rename of the key.\n","example":"A product with code 'PROD-001' already exists"}}},"ProductBulkCreateItem":{"type":"object","required":["index","status"],"description":"How one submitted product ended up.","properties":{"index":{"type":"integer","description":"Position of the product in the submitted list (0-based).","example":0},"code":{"type":"string","description":"Echo of the `code` that was submitted, so the row can be recognised without counting positions. Absent when the product carried none.","example":"PROD-001"},"name":{"type":"string","description":"Echo of the `name` that was submitted.","example":"Consultoría"},"status":{"type":"string","enum":["CREATED","DUPLICATE","INVALID"],"description":"Outcome of this row:\n- `CREATED`: created; `product_id` carries its identifier\n- `DUPLICATE`: a product with that code already exists\n- `INVALID`: some field does not pass the domain's validation — a rate BeeL does not\n  accept (VAT at 13%), an empty name, a negative price, an incoherent surcharge/regime\n\nThere is no \"unknown failure\" value: anything the domain does not know how to report is\nnot turned into a row, it fails the request with a `500`.\n","example":"CREATED"},"product_id":{"allOf":[{"$ref":"#/components/schemas/UUID"}],"description":"Identifier of the product this row created. **Present only when the row actually created\nsomething**; absent otherwise.\n"},"error":{"allOf":[{"$ref":"#/components/schemas/ProductBulkCreateError"}],"description":"Present only when the product was not created."}}},"ProductBulkCreateStatistics":{"type":"object","required":["total_processed","created","duplicates","invalid"],"description":"Counters derived by counting the entries of `products_creation`, never accumulated while\nprocessing. Each row falls into exactly one of `created`, `duplicates` and `invalid`, so\nthose three add up to `total_processed`.\n","properties":{"total_processed":{"type":"integer","description":"Products submitted.","example":10},"created":{"type":"integer","description":"Products created.","example":8},"duplicates":{"type":"integer","description":"Rows whose code already existed.","example":1},"invalid":{"type":"integer","description":"Rows some field of which did not pass validation.","example":1}}},"ProductBulkCreateLegacyError":{"type":"object","deprecated":true,"description":"**Deprecated.** Legacy shape of a row that was not created.","properties":{"index":{"type":"integer"},"code":{"type":"string"},"name":{"type":"string"},"error":{"type":"string","description":"Text resolved to the language of the request. Read `products_creation[].error.code` to branch on."}}},"ProductBulkCreateLegacySummary":{"type":"object","deprecated":true,"description":"**Deprecated.** Read `statistics`.","properties":{"total_processed":{"type":"integer"},"successful":{"type":"integer"},"failed":{"type":"integer"}}},"ProductBulkCreateResult":{"type":"object","required":["metadata","products_creation","statistics","created_products"],"description":"Report of a bulk creation of products, in the same shape every batch operation of this API\nuses (ADR-0011): `metadata` (what the operation was), **one entry per product submitted** in\nits original position and never filtered, and `statistics` derived by counting those\nentries.\n\n**Atomicity: partial.** Valid products are created; the rest are reported row by row and the\nbatch is not rolled back. (Creating customers in bulk is all-or-nothing instead — that is a\ndeliberate choice per resource, not an inconsistency.)\n\n**HTTP status:** always `201` when the request was processed, even if not a single product\ncould be created. The code says whether the request was processed, not whether every row\nwent well.\n","properties":{"metadata":{"$ref":"#/components/schemas/ProductBulkCreateMetadata"},"products_creation":{"type":"array","description":"One entry per product submitted, in the order they were sent and never filtered: a\nproduct that was rejected keeps its place and its `index`, so the response lines up\nwith the request.\n","items":{"$ref":"#/components/schemas/ProductBulkCreateItem"}},"statistics":{"$ref":"#/components/schemas/ProductBulkCreateStatistics"},"created_products":{"type":"array","description":"The products that were created, in submission order.","items":{"$ref":"#/components/schemas/Product"}},"errors":{"type":"array","deprecated":true,"description":"**Deprecated.** Projection of the entries of `products_creation` that were not created,\nin the same order. Read `products_creation`, which also reports the rows that worked and\nwhose `error.code` is stable. Its `error` field used to carry a raw i18n key\n(`error.producto.duplicado`) and now carries the text resolved to the language of the\nrequest.\n","items":{"$ref":"#/components/schemas/ProductBulkCreateLegacyError"}},"summary":{"allOf":[{"$ref":"#/components/schemas/ProductBulkCreateLegacySummary"}],"deprecated":true,"description":"**Deprecated.** Read `statistics`."}}},"AccountRole":{"type":"string","description":"Who administers the account. Independent of `access_level`, which says how much access someone has to a given company.\n\n`OWNER` — full control, including billing, API keys and transferring ownership. Exactly one per account, so it is never an accepted value when you SET a role (inviting a member or changing one's role): both reject it with `422 OWNER_ROLE_NOT_ASSIGNABLE`. Ownership moves only through `PUT /v1/accounts/{account_id}/owner`.\n`ADMIN` — everything an `OWNER` can do, except transferring ownership.\n`MEMBER` — no account administration. Access to each company is granted individually and reported as `access_level`; a member only sees the companies granted to them.","enum":["OWNER","ADMIN","MEMBER"]},"MemberGrant":{"type":"object","description":"A member's access to a specific company, as read surfaces report it. It is `GrantAssignment` plus the company's display name, so a client can name the company without resolving every identifier against the companies of the account.","required":["company_id","access_level"],"properties":{"company_id":{"type":"string","format":"uuid","description":"Unique identifier (UUID) of the company within the account."},"access_level":{"type":"string","description":"Access the member has over this company.","enum":["VIEW","OPERATE"]},"company_name":{"type":"string","nullable":true,"description":"Legal name of the company; the trade name is never used here. `null` when the company has no legal name yet (possible before onboarding completes) or no longer exists. Treat it as optional: a client must not require it to be present. Never falls back to the identifier: `company_id` already carries it."}}},"MemberPermissions":{"type":"object","description":"Which management actions you may take on THIS member, resolved by the server for the authenticated caller. Use it to decide what to offer instead of discovering the answer through a `403`. It is advisory: every action is enforced server-side regardless, so you never need to reimplement the role policy.","required":["can_change_role","assignable_roles","can_remove","can_transfer_ownership","can_edit_grants"],"properties":{"can_change_role":{"type":"boolean","description":"Whether the caller may change this member's role (to one of `assignable_roles`)."},"assignable_roles":{"type":"array","description":"Roles the caller may assign to this member (excludes the current one, the ones reserved to a higher role, and always `OWNER` — ownership is handed over with `can_transfer_ownership`, not by assigning the role).","items":{"$ref":"#/components/schemas/AccountRole"}},"can_remove":{"type":"boolean","description":"Whether the caller may remove this member from the account."},"can_transfer_ownership":{"type":"boolean","description":"Whether the caller (OWNER) may transfer ownership to this member."},"can_edit_grants":{"type":"boolean","description":"Whether the caller may edit this member's company grants (MEMBER only)."}}},"AccountMember":{"type":"object","description":"A member of the account (a person with access) and their role.","required":["member_id","person_id","account_role","permissions"],"properties":{"member_id":{"type":"string","format":"uuid","description":"Membership UUID — the handle for every member operation (change role, set grants, remove, transfer ownership). Identifies this person **in this account**."},"person_id":{"type":"string","format":"uuid","description":"Person UUID — the stable identity of the human. The same person keeps this `person_id` in every account they belong to (with a different `member_id` in each), so you can recognise the same person across the accounts you manage. Not needed to call any endpoint."},"email":{"type":"string","format":"email","nullable":true,"description":"Email address of the person."},"account_role":{"$ref":"#/components/schemas/AccountRole"},"grants":{"type":"array","description":"The member's company grants (empty for OWNER/ADMIN, who have implicit access to all).","items":{"$ref":"#/components/schemas/MemberGrant"}},"permissions":{"$ref":"#/components/schemas/MemberPermissions"}}},"ChangeMemberRoleRequest":{"type":"object","required":["account_role"],"properties":{"account_role":{"$ref":"#/components/schemas/AccountRole"}}},"PutMemberGrantRequest":{"type":"object","description":"The access a member gets over ONE company. The company is the one in the path, so it is not repeated in the body, and the member's other grants are untouched.","required":["access_level"],"properties":{"access_level":{"type":"string","description":"Access the member gets over this company.","enum":["VIEW","OPERATE"]}}},"GrantAssignment":{"type":"object","description":"A member's access to a specific company.","required":["company_id","access_level"],"properties":{"company_id":{"type":"string","format":"uuid","description":"Unique identifier (UUID) of the company within the account."},"access_level":{"type":"string","description":"Access the member gets over this company. `NONE` is not accepted here: a grant that\ngrants nothing is not a grant. Remove access by deleting the grant\n(`DELETE /v1/accounts/{account_id}/members/{member_id}/grants/{company_id}`).\n","enum":["VIEW","OPERATE"]}}},"SetAccountOwnerRequest":{"type":"object","description":"The member who is to own the account.","required":["member_id"],"properties":{"member_id":{"type":"string","format":"uuid","description":"Membership UUID of the member who becomes OWNER."}}},"InvitationStatus":{"type":"string","description":"Lifecycle state of a member invitation. `PENDING`: active and not yet expired; `ACCEPTED`: accepted by the invitee; `REVOKED`: cancelled before acceptance; `EXPIRED`: passed the expiry date without being accepted.","enum":["PENDING","ACCEPTED","REVOKED","EXPIRED"]},"InvitationSummary":{"type":"object","description":"Summary of a member invitation for the sender's list view. The acceptance token is not exposed here — it is shown only once, at creation time.","required":["invitation_id","invited_email","account_role","status","created_at","expires_at"],"properties":{"invitation_id":{"type":"string","format":"uuid"},"invited_email":{"type":"string","format":"email"},"account_role":{"$ref":"#/components/schemas/AccountRole"},"status":{"$ref":"#/components/schemas/InvitationStatus"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}},"CreateInvitationRequest":{"type":"object","description":"Invites a person to join the active account.","required":["invited_email","account_role"],"properties":{"invited_email":{"type":"string","format":"email","minLength":1,"description":"Email address of the invited person."},"account_role":{"$ref":"#/components/schemas/AccountRole"},"send_email":{"type":"boolean","default":false,"description":"If `true`, an invitation email with the acceptance link is sent to `invited_email` in addition to returning the token. Defaults to `false` (you deliver the token/link yourself)."},"grants":{"type":"array","default":[],"x-field-extra-annotation":"@jakarta.validation.constraints.NotNull","description":"Initial grants (only when `account_role` is `MEMBER`). Omit it, or send `[]`, to invite with no company access yet (granted later). An explicit `null` is rejected with `422` `VALIDATION_ERROR`.","items":{"$ref":"#/components/schemas/GrantAssignment"}}}},"Invitation":{"type":"object","description":"A created invitation. `token` is the single-use acceptance secret, returned only once (never exposed again); deliver it to the invitee. `invitation_url` is the ready-to-deliver acceptance link BeeL builds for the current environment from that same token.","required":["invitation_id","invited_email","account_role","expires_at","token"],"properties":{"invitation_id":{"type":"string","format":"uuid"},"invited_email":{"type":"string","format":"email"},"account_role":{"$ref":"#/components/schemas/AccountRole"},"expires_at":{"type":"string","format":"date-time"},"token":{"type":"string","description":"Single-use acceptance token (shown once); the source of truth of the invitation."},"invitation_url":{"type":"string","format":"uri","nullable":true,"description":"Ready-to-use acceptance link for the invitee, built by BeeL for the current environment (no need to assemble `/aceptar?token=` yourself). Derived from `token`, which remains the single-use source of truth. `null` only if no token was issued."}}},"ProvisioningStatus":{"type":"string","enum":["PROVISIONED","CLAIMED","ACTIVE"],"description":"Lifecycle stage of a provisioned account. The claim gates the stage: `PROVISIONED` (created, not yet claimed — the holder has not set a password / taken ownership, even if a NIF was seeded at provisioning or you invoice on their behalf with `OPERATE`); `CLAIMED` (the holder set their password and took ownership, no NIF yet); `ACTIVE` (claimed and has at least one NIF — can operate under their own ownership)."},"AccessLevel":{"type":"string","description":"How much access an actor has to an account or a company. The same three values are used everywhere access is granted or reported — whether the actor is a member of the account or a provisioner managing it on someone's behalf.\n\n`NONE` — no access to the data.\n`VIEW` — read invoices, customers, products, series and fiscal data.\n`OPERATE` — everything in `VIEW`, plus creating and editing them. Issuing invoices for an account you manage additionally requires a signed fiscal representation from the account holder (see `/v1/accounts/{account_id}/companies/{company_id}/representation`).\n\nAccess level never affects billing: whoever provisioned an account pays for its subscription regardless of the level they keep over it.","enum":["NONE","VIEW","OPERATE"]},"ClaimStatus":{"type":"string","enum":["NOT_ISSUED","PENDING","EXPIRED","CLAIMED"],"description":"State of the account's claim link — the handover of the account to its holder. It answers the only question the manager has in front of a list: *can this person still take their account, or do I have to send them a new link?*\n\n`NOT_ISSUED` (no claim token was ever issued: the account was provisioned without `email` and has no holder yet); `PENDING` (a token is live — the holder can still claim); `EXPIRED` (the last token issued has lapsed, so the link in the holder's inbox no longer works); `CLAIMED` (the holder set their password and took ownership — nothing left to claim). `EXPIRED` and `NOT_ISSUED` are deliberately distinct: \"it ran out\" and \"it was never sent\" call for the same action but are not the same fact, and reporting one as the other would make the list lie."},"AccountClaim":{"type":"object","description":"Claim state of the account and, when there is a token to speak of, when it stops (or stopped) working. The token itself is **never** returned here: it is shown once, at the moment it is created (`POST /v1/accounts` or `POST /v1/accounts/{account_id}/claim-tokens`). A read that handed out live redemption links for a whole portfolio would be a needless prize; if the link was lost, issue a new one — doing so invalidates the previous.","required":["status"],"properties":{"status":{"$ref":"#/components/schemas/ClaimStatus"},"expires_at":{"type":"string","format":"date-time","nullable":true,"description":"When the last issued token stops (`PENDING`) or stopped (`EXPIRED`) working. Tokens last 30 days from issue. `null` for `NOT_ISSUED` and `CLAIMED`, where there is no expiry to report."}}},"ManagedAccountSummary":{"type":"object","description":"An account you provisioned, with its lifecycle status and the access you hold.","required":["account_id","external_ref","access_level","status","claim","representation_signed","created_at"],"properties":{"account_id":{"type":"string","format":"uuid"},"external_ref":{"type":"string","description":"The id you assigned to this account in your own system when provisioning it."},"display_name":{"type":"string"},"access_level":{"$ref":"#/components/schemas/AccessLevel"},"status":{"$ref":"#/components/schemas/ProvisioningStatus"},"claim":{"allOf":[{"$ref":"#/components/schemas/AccountClaim"}],"description":"State of the account's claim link. `status` here refines the `PROVISIONED` stage of `status` above, which cannot tell \"the holder still has a working link\" from \"their link expired a month ago\" — the difference between waiting and having to act."},"company_id":{"type":"string","format":"uuid","nullable":true,"description":"The account's **single** company id (a UUID) — the value for the `BeeL-Active-Company` header when operating on this account. It is a **scalar, not a list**: it is present when the account holds exactly one company, and `null` otherwise (two or more, or none left). An account provisioned without `tax_profile` already holds one company, still without a NIF, so its id is returned; a company without a NIF does not make a claimed account `ACTIVE`. For a multi-NIF account, list them with `GET /v1/accounts/{account_id}/companies`. Same meaning as `company_id` in the response to `POST /v1/accounts`."},"representation_signed":{"type":"boolean","description":"Whether the account holder has signed at least one fiscal representation."},"created_at":{"type":"string","format":"date-time"}}},"ProvisionTaxProfile":{"type":"object","description":"The account's fiscal identity, to set it up ready to invoice. Same field vocabulary as `POST /v1/accounts/{account_id}/companies` (`CreateCompanyRequest`). Tax rates fall back to system defaults when omitted; the invoice series is always created with system defaults (the holder edits it later).","required":["nif","legal_name","entity_type","address"],"properties":{"nif":{"type":"string","description":"Spanish tax id (NIF/CIF). Validated against the AEAT census; an invalid NIF returns 422."},"legal_name":{"type":"string","description":"Registered fiscal name."},"entity_type":{"$ref":"#/components/schemas/EntityType"},"address":{"$ref":"#/components/schemas/Address"},"legal_form":{"type":"string","description":"Legal form (e.g. `SL`). Recommended for `LEGAL_ENTITY`."},"legal_representative":{"$ref":"#/components/schemas/LegalRepresentative"},"trade_name":{"type":"string","description":"Commercial/trade name shown on invoices (defaults to `legal_name`)."},"default_main_tax":{"$ref":"#/components/schemas/TaxInfo"},"default_irpf_rate":{"type":"number","description":"Default IRPF withholding percentage (use `0` for exempt). Omit it and the company is created with **no withholding at all** — BeeL never assumes a rate nobody declared. You know your account holder's regime; set it explicitly when they do withhold."}}},"ProvisionAccountRequest":{"type":"object","description":"Request to provision a new account. You retain management access at the level in `access_level` (defaults to `NONE`).\n\nThere are two ways to integrate and both are supported. **With `email`** the account is born with a holder (a person who can log in) and the response carries a `claim_token` / `claim_url` to hand over. **Without `email`** the account and its NIF are created with **no person at all** — for platforms whose self-employed workers will never use BeeL. themselves — and the response carries no token. Inviting someone to claim it is a deferred, optional step: `POST /v1/accounts/{account_id}/claim-tokens`. Nothing is invented: BeeL. never fabricates a placeholder email.","required":["display_name","external_ref"],"properties":{"email":{"type":"string","format":"email","description":"Optional. Email address of the account holder, used as their login. Omit it to create the account **without a person**: no login, no `person_id` and no `claim_token`. You can add the holder later with `POST /v1/accounts/{account_id}/claim-tokens`. Required when `send_email` is `true` (there is nobody to write to otherwise) — else `422`."},"display_name":{"type":"string","minLength":1,"maxLength":255,"description":"Human-readable name for the account. Must not be blank."},"external_ref":{"type":"string","description":"Your own identifier for this account in your system. Used as an idempotency key: re-provisioning with the same `external_ref` returns the existing account (201, not 409)."},"language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"description":"Preferred language for the account holder. Defaults to `es`."},"access_level":{"allOf":[{"$ref":"#/components/schemas/AccessLevel"}],"x-field-extra-annotation":"@com.fasterxml.jackson.annotation.JsonAlias(\"access\")","description":"Optional. The access you retain over this account after provisioning. Defaults to `NONE` (you cover their subscription but cannot access their data). Change it later via PATCH /v1/accounts/{account_id}/access-level. This field was previously named `access`; the old name is still accepted as an alias for backwards compatibility and will be withdrawn in a future major version — send `access_level`."},"tax_profile":{"allOf":[{"$ref":"#/components/schemas/ProvisionTaxProfile"}],"description":"Optional fiscal identity. When present, the account is created **ready to invoice** in one call: its company record, a default invoice series and VeriFactu config are set up atomically, and the response returns `company_id` (the value for the `BeeL-Active-Company` header when issuing invoices). Omit it to create an empty account the holder completes on claim. **Required when `access_level` is `OPERATE`** (issuing on their behalf needs a NIF) — else `422`."},"send_email":{"type":"boolean","default":false,"description":"Optional. When `true`, BeeL emails the account holder a claim link (`/reclamar?token=...`) so they can set their password and take ownership. Defaults to `false`: by default you receive the `claim_token` in the response and deliver it yourself. Requires a deliverable `email`: sending `true` without one returns `422`."}}},"ProvisionAccountResult":{"type":"object","description":"Result of a successful account provision. `claim_token` is a single-use secret to deliver to the account holder so they can set their password and take ownership. It is returned only once; `null` if the account was already claimed, `null` when the account was provisioned without `email` (there is no holder to hand it to yet), and `null` when a repeated provision found a claim link still valid (`claim_link_already_issued: true`). `claim_url` is the ready-to-use claim link BeeL builds for the current environment; `status` disambiguates a `null` `claim_token` (already claimed) from an error.","required":["account_id","status","claim_link_already_issued"],"properties":{"person_id":{"type":"string","format":"uuid","nullable":true,"description":"The account holder. `null` when the account was provisioned **without `email`**: it has no person yet, and gains one when a claim token is issued."},"account_id":{"type":"string","format":"uuid"},"status":{"allOf":[{"$ref":"#/components/schemas/ProvisioningStatus"}],"description":"Lifecycle stage of the account right after this call. A newly provisioned account is `PROVISIONED` (not yet claimed). Combined with `claim_token`, it is self-explanatory: a `null` `claim_token` with `status: CLAIMED`/`ACTIVE` means the holder already took ownership (not an error)."},"claim_token":{"type":"string","nullable":true,"description":"Single-use claim token (shown once); `null` if the account is already claimed, or if a claim link issued earlier is still valid (see `claim_link_already_issued`)."},"claim_url":{"type":"string","format":"uri","nullable":true,"description":"Ready-to-use claim link for the account holder, built by BeeL for the current environment (no need to assemble `/reclamar?token=` yourself). `null` when `claim_token` is `null`."},"claim_link_already_issued":{"type":"boolean","description":"`true` when this call repeated the provision of an unclaimed account that already has a claim link still valid (pending and not expired). That link is **kept working**: nothing is issued or revoked, `claim_token` and `claim_url` come back `null`, and no email is re-sent even with `send_email: true` (there is no token in clear to send). The link you already handed out is the one the holder uses. To replace it on purpose, call `POST /v1/accounts/{account_id}/claim-tokens`, which revokes the previous one. `false` otherwise.","example":false},"company_id":{"type":"string","format":"uuid","nullable":true,"description":"The account's company id (a UUID) — send it in the `BeeL-Active-Company` header to operate on this account. Every account is born with one company: with a `tax_profile` it is ready to invoice; without one it has no NIF yet (the holder registers it on claim), and its issuing readiness reports `COMPANY_HAS_NO_NIF` until then. `null` only when resending an `external_ref` whose account now holds two or more companies."}}},"ChangeAccessLevelRequest":{"type":"object","description":"Updates the access you hold over an account you provisioned.","required":["access_level"],"properties":{"access_level":{"$ref":"#/components/schemas/AccessLevel"}}},"CreateClaimTokenRequest":{"type":"object","description":"Optional body for issuing a claim token. Send `email` when the account has **no holder yet** (it was provisioned without one): the person is created at that point. Omit the body entirely to re-issue the token for the holder the account already has.","properties":{"email":{"type":"string","format":"email","description":"The holder's email address, used as their login. **Required when the account has no holder** (else `422`). If the account already has one, it must match theirs — a different address returns `409 CLAIM_TOKEN_HOLDER_MISMATCH` rather than silently replacing the holder."},"language":{"allOf":[{"$ref":"#/components/schemas/Language"}],"description":"Preferred language for a holder created by this call. Defaults to `es`. Ignored when the account already has a holder."}}},"ClaimTokenResult":{"type":"object","description":"A freshly issued claim token. `claim_token` is shown **once** and cannot be read back; any token issued earlier for this account is now invalid.","required":["person_id","claim_token","claim_url","expires_at"],"properties":{"person_id":{"type":"string","format":"uuid","description":"The account holder — created by this call if the account had none."},"claim_token":{"type":"string","description":"Single-use claim token. Deliver it to the holder; it is never returned again."},"claim_url":{"type":"string","format":"uri","description":"Ready-to-use claim link built by BeeL. for the current environment."},"expires_at":{"type":"string","format":"date-time","description":"When the token stops working (30 days from issue). Issue a new one to replace it."}}},"ProvisioningUsage":{"type":"object","description":"Provisioning usage for the calling provisioner. `nifs` is the billable count, and the billable unit is the **provisioned account**, not the real NIF: every account you provision counts as 1 — including empty/unclaimed ones (each provisioned account is born with a placeholder company row), whether or not the holder has registered a real NIF yet. So a newly provisioned empty account contributes 1 to `nifs`, not 0.","required":["provisioned_accounts","nifs"],"properties":{"provisioned_accounts":{"type":"integer","format":"int64","description":"Total accounts provisioned by this provisioner.","example":1000},"nifs":{"type":"integer","format":"int64","description":"Billable count: one per provisioned account (each has a company row, placeholder or real), including empty/unclaimed accounts. Equals `provisioned_accounts` unless an account holds more than one NIF.","example":1000}}},"DocumentType":{"type":"string","enum":["UNASSIGNED","STANDARD","SIMPLIFIED","CORRECTIVE","SIMPLIFIED_CORRECTIVE","PROFORMA"],"description":"Document type associated with a series. Values mirror `InvoiceType`,\nso the series a document needs is named exactly like the document:\n- UNASSIGNED: Legacy value of series created before types existed. A series numbers only\n  documents of its own type, so an `UNASSIGNED` series numbers none\n  (`422 SERIES_INCOMPATIBLE_DOC_TYPE`); give it a type to keep using it. No series can be\n  created with it or moved to it (`422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`). The live\n  ones were given the type they numbered most.\n- STANDARD: Standard invoice\n- SIMPLIFIED: Simplified invoice\n- CORRECTIVE: Corrects or cancels a previous standard invoice (R1–R4)\n- SIMPLIFIED_CORRECTIVE: Corrects a simplified invoice (R5). Spanish law requires R5 to be\n  numbered apart from R1–R4, so they have a series type of their own\n- PROFORMA: Proforma (commercial document, non-fiscal numbering)\n"},"AccountImportOptions":{"type":"object","description":"Settings that apply to **every row** of the file. They live here and not as columns because an agency onboards all of its managed accounts the same way: a column nobody varies is a column everybody mistypes.\n\nTravels as an `application/json` part named `options` inside the multipart body — send it with its own `Content-Type: application/json` (`-F 'options=…;type=application/json'` in curl). Omit the part entirely to take the defaults.","properties":{"access_level":{"allOf":[{"$ref":"#/components/schemas/AccessLevel"}],"description":"The access you keep over every account of this file. Defaults to `OPERATE` — not to `NONE` as in `POST /v1/accounts`, and the difference is deliberate: a file-driven import is re-run to reconcile what is missing, and reconciling an account's series and customers is a write that `VIEW` cannot do. An account you already manage with less than this is reported `BLOCKED` and skipped: once its holder has claimed it, only they can raise your access again."},"series":{"type":"array","maxItems":8,"description":"Invoice series every account must end up with, declared once. Each entry is a `CreateSeriesRequest` exactly as `POST /v1/companies/{company_id}/series` takes it, with one addition: the token `{REF}` in `name` and `format` is replaced with the row's `external_ref` **before** the series is created, which is how a single declaration numbers every account distinguishably. `{REF}` is vocabulary of this endpoint only — what reaches the series is a literal format, so nothing downstream has to learn a new variable. A row whose `external_ref` would produce a format outside the series grammar (uppercase letters, digits, `-`, `_`, `/`, `:`) is reported and its series left alone.\n\nLeave it out and each account keeps the default series its activation seeds.","items":{"$ref":"#/components/schemas/CreateSeriesRequest"}},"apply_customers_to_own_company":{"type":"boolean","default":false,"description":"Load the shared customers file into **your own** company too, not only into the managed accounts the file describes. Off by default: an import of accounts writes on the accounts it imports, and a file that quietly also wrote on your own customers would be a surprise nobody asked for.\n\n**Your own company is the one in focus for this request** — the same one every company-scoped write of the API uses, and never one this endpoint picks for you. With several NIFs and none in focus the whole import is refused with `403` `ACTIVE_COMPANY_REQUIRED`, decided **before the files are read**: no account is provisioned and nothing is billed. Sending the option without `customers_file` is a `422`, decided in the same place — there is nothing to apply.\n\nWith an API key it needs `customers:write` on top of the `accounts:write` the operation already requires. It writes customers; a key that may not write customers must not get to through an accounts import. A dashboard session is governed by the role's capabilities over the company in focus, as everywhere else.\n\nWhat it did comes back in `own_company_customers`, apart from the per-account counters."}}},"AccountImportUpload":{"type":"object","required":["accounts_file"],"properties":{"accounts_file":{"type":"string","format":"binary","description":"CSV with one managed account per row, in the format of `GET /v1/templates/account-import`: UTF-8, header names matched case-insensitively, and `,`, `;` or tab accepted as separator.\n\nThe header row does not have to be the first line. A spreadsheet almost never starts on it — there is a title, sometimes a blank line — and that preamble travels ahead of the data when the sheet is exported. Everything before the first line carrying the required columns is ignored, within the first 20 lines. Reported `row_number`s still count from the top of the file, so they match what you see when you open it."},"customers_file":{"type":"string","format":"binary","description":"Optional CSV of customers to apply to **every** account of this import, in the format of `GET /v1/templates/customer-import`. Idempotent by tax id. Its preamble is skipped the same way — it comes out of the same spreadsheet."},"options":{"$ref":"#/components/schemas/AccountImportOptions"}}},"AccountImportMetadata":{"type":"object","required":["is_dry_run","total_rows","processing_time_ms","environment"],"properties":{"is_dry_run":{"type":"boolean","description":"`true` for the preview (nothing was written), `false` for the import. It is what tells the two answers apart when they are stored side by side."},"total_rows":{"type":"integer","description":"Data rows found in the accounts file, header excluded.","example":19},"processing_time_ms":{"type":"integer","format":"int64","example":18400},"accounts_filename":{"type":"string","nullable":true,"description":"Name of the uploaded accounts file, as your client sent it."},"customers_filename":{"type":"string","nullable":true,"description":"Name of the uploaded customers file; `null` when none was sent."},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"description":"The mode this import ran in, taken from the credential. Restated because it decides whether the NIFs were switched on against the real AEAT and whether anything was billed."}}},"AccountImportItemStatus":{"type":"string","description":"Outcome of one row. The same five values are used by the preview and by the import — the\npreview reports what the import would reach — so no value tells you which operation\nproduced the payload; `metadata.is_dry_run` does.\n\n* `VALID` — complete and reachable. Provisioned by the import; would be by a preview.\n* `WARNING` — the same, with non-blocking warnings.\n* `ALREADY_EXISTS` — an account with this `external_ref` is already yours. A documented\n  success, not a collision: provisioning is idempotent by `external_ref`, so nothing is\n  created and nothing is billed again. On an import the row is still reconciled (Live,\n  series, customers), which is what makes re-uploading the same file useful.\n* `BLOCKED` — well formed but not executable. `errors` says why, and it is always something\n  to resolve outside the file.\n* `ERROR` — the row's own data is wrong. `errors` says which column.\n","enum":["VALID","WARNING","ALREADY_EXISTS","BLOCKED","ERROR"],"example":"VALID"},"LiveActivationVerdict":{"type":"string","description":"The server's complete answer to \"may THIS actor activate a NIF in Live on this account,\nand if not, why?\". Published so the UI never has to re-derive what the server will decide\nwhen the button is pressed — a screen that computes it on its own ends up disagreeing, and\nthe disagreement shows up as a green button that answers 403.\n\nThe first four values are predicated on the PAYING account (a managed account inherits its\nmanager's entitlement); the last two on the ACTOR and the account in focus.\n\n* `ENTITLED` — live plan/contract or card on file. Activation is immediate.\n* `CHECKOUT_REQUIRED` — no card yet: go through the checkout.\n* `PLAN_ACTIVATION_REQUIRED` — fixed-quota plan on trial: the act is ACTIVATING THE PLAN,\n  not the per-NIF checkout (that one charges for a NIF the quota already includes).\n* `PAYMENT_REQUIRED` — unpaid (payment retries exhausted): no new checkout, regularise the existing one.\n* `NOT_BILLING_SUBJECT` — the actor is not the billing subject. Informative, no action.\n* `BILLING_MANAGED_EXTERNALLY` — an agency handles the billing. Informative, no action.\n* `CONTACT_SALES` — API plan account with two or more active NIFs and no agreed price:\n  going Live (checkout or activation) needs a price agreed with sales first. The\n  activation answers `403 CONTACT_SALES_REQUIRED` with `details.reason =\n  LIVE_WITH_MULTIPLE_NIFS` and `details.salesUrl`.\n","enum":["ENTITLED","CHECKOUT_REQUIRED","PLAN_ACTIVATION_REQUIRED","PAYMENT_REQUIRED","NOT_BILLING_SUBJECT","BILLING_MANAGED_EXTERNALLY","CONTACT_SALES"],"example":"CHECKOUT_REQUIRED"},"AccountImportSeriesAction":{"type":"string","description":"* `ALREADY_EXISTS` — the account already numbers like this. Nothing was touched. A series\n  counts as present when it numbers identically, whether it is written with `{CODIGO}` or\n  with the code spelled out.\n* `CREATED` — the series was created (in a preview: would be).\n* `MANUAL_REVIEW_REQUIRED` — neither the declared code nor its `<code>2` fallback is free,\n  or the resolved format falls outside the series grammar. Inventing a third code would be\n  guessing, and changing the format of a series that has already numbered a document is not\n  something an import may do. Resolve it on the account and re-upload.\n","enum":["ALREADY_EXISTS","CREATED","MANUAL_REVIEW_REQUIRED"]},"AccountImportSeriesOutcome":{"type":"object","description":"What one declared series did on one account.","required":["document_type","action"],"properties":{"document_type":{"$ref":"#/components/schemas/DocumentType"},"action":{"$ref":"#/components/schemas/AccountImportSeriesAction"},"code":{"type":"string","nullable":true,"description":"Code the series was created under — not necessarily the declared one, when that was already taken by a different numbering."},"format":{"type":"string","nullable":true,"description":"Format with `{REF}` already resolved to this row's `external_ref`."}}},"AccountImportCustomersOutcome":{"type":"object","description":"What the shared customers file did on one account. `already_existed` is the number that makes re-uploading cheap: a customer whose tax id the account already knows is counted, not duplicated.","required":["created","already_existed","failed"],"properties":{"created":{"type":"integer","example":3},"already_existed":{"type":"integer","example":37},"failed":{"type":"integer","example":0}}},"AccountImportIssueCode":{"type":"string","description":"Machine-readable reason for an issue. New values are added as the import learns to explain\nmore; treat an unknown one as a generic problem rather than failing.\n\nRead straight off the row: `REQUIRED_FIELD_MISSING` (the column is there and its **cell** is\nempty — a missing column is a whole-file error, not a row one), `EXTERNAL_REF_DUPLICATED`,\n`ENTITY_TYPE_UNKNOWN`, `NIF_INVALID_FORMAT`, `EMAIL_INVALID`, `POSTAL_CODE_INVALID`,\n`IBAN_INVALID`, `IRPF_NOT_NUMERIC`, `LANGUAGE_UNSUPPORTED`.\n\n`IRPF_NOT_ALLOWED` — the rate is a number but not one of `IrpfPercentage`, or not one the\nrow's NIF can bear: the same check as its invoices (see `WithholdingOptions`), so a\ncompany (`B…`) declaring 15 % is rejected here. It is an **error**, not a warning: letting it through buys a green preview and an\naccount that fails on its first invoice months later, when nobody connects the two.\n\n`POSTAL_CODE_PADDED` — a warning: the postal code was padded with leading zeros, which is\nalmost always the right repair but does change the value, and the first digit picks the\nprovince.\n\nCensus: `NIF_NOT_IN_CENSUS` — the tax id is not in the AEAT register or is no longer active.\nFor an **individual** it also covers a legal name that does not match the one on file; for a\n**legal entity** the name is not checked at all, because the AEAT identifies a company by its\nCIF alone and has no answer that means \"this name is not the one\".\n\nState of the account rather than of the file, and the ones that produce `BLOCKED`:\n`ACCOUNT_ALREADY_EXISTS` (a warning, not a blocker), `ACCESS_LEVEL_INSUFFICIENT`,\n`LIVE_ACTIVATION_NOT_ENTITLED`, `NIF_ALREADY_LIVE_ELSEWHERE`, `NIF_LIMIT_REACHED`,\n`PROVISIONING_EMAIL_ALREADY_REGISTERED`, `PROVISIONING_ACCOUNT_CLAIMED` — each the wire code\nthe equivalent single-account call answers with, so the remedy is the one already documented\nthere.\n\n`NIF_LIMIT_REACHED` — switching this row on in Live would exceed the \"up to N NIFs\" cap of\nyour agreement. The cap is cumulative across the file: rows are counted in order, and only\nrows that would be switched on in Live take a place, so a row whose NIF already invoices in\nLive, or that is blocked for another reason, never does. In the preview, and when the\nimport already knew it before writing, `value` is the cap. If the cap fills up while the\nimport is running (another activation took the last place between planning and writing),\n`value` carries the wire code `NIF_LIMIT_REACHED` instead.\n\nSeries: `SERIES_CODE_TAKEN`, `SERIES_MANUAL_RESOLUTION_REQUIRED` (the declared code and its\n`<code>2` fallback are both taken), `SERIES_CODE_DUPLICATED` (this same import declares the\ncode twice), `SERIES_FORMAT_INVALID` (the format, once `{REF}` is resolved, cannot number —\nno `{NUM}`, or the `external_ref` added characters the grammar rejects) and\n`SERIES_DEFAULT_REPLACED`. The first four leave the account **without** that series, and say\nso.\n\nCustomers file: `CUSTOMER_ROW_REJECTED` — a row of the shared customers file that will not\nload anywhere. One issue per offending column, each with the message the customer import\nwrote for it; a row rejected with no column-level detail carries a single one. The census\ncode above is used here too, but only ever on the `nif` column.\n\nAnything else: `UNEXPECTED_ROW_FAILURE`, with the detail in `value`.\n","enum":["REQUIRED_FIELD_MISSING","EXTERNAL_REF_DUPLICATED","ENTITY_TYPE_UNKNOWN","NIF_INVALID_FORMAT","NIF_NOT_IN_CENSUS","EMAIL_INVALID","POSTAL_CODE_INVALID","POSTAL_CODE_PADDED","IBAN_INVALID","IRPF_NOT_NUMERIC","IRPF_NOT_ALLOWED","LANGUAGE_UNSUPPORTED","ACCOUNT_ALREADY_EXISTS","ACCESS_LEVEL_INSUFFICIENT","LIVE_ACTIVATION_NOT_ENTITLED","NIF_ALREADY_LIVE_ELSEWHERE","NIF_LIMIT_REACHED","PROVISIONING_EMAIL_ALREADY_REGISTERED","PROVISIONING_ACCOUNT_CLAIMED","SERIES_CODE_TAKEN","SERIES_CODE_DUPLICATED","SERIES_FORMAT_INVALID","SERIES_MANUAL_RESOLUTION_REQUIRED","SERIES_DEFAULT_REPLACED","CUSTOMER_ROW_REJECTED","UNEXPECTED_ROW_FAILURE"],"example":"LIVE_ACTIVATION_NOT_ENTITLED"},"AccountImportIssue":{"type":"object","description":"One thing that happened to a row, with a stable `code` to branch on and a `message` already written in the language of the request. Branch on the code; the message is for people.","required":["code","message"],"properties":{"code":{"$ref":"#/components/schemas/AccountImportIssueCode"},"column":{"type":"string","nullable":true,"description":"Column of the file involved; `null` when the issue is about the row as a whole.","example":"direccion_codigo_postal"},"value":{"type":"string","nullable":true,"description":"The offending value, already normalised."},"message":{"type":"string","description":"Human-readable explanation, in the language of the request. Issues coming from the customers file are the exception: they carry the wording the customer import itself produced, which is Spanish — the same text `POST /v1/companies/{company_id}/customers/imports/preview` returns. Re-labelling them to translate would collapse every error of a row into one repeated sentence, which is worse than one untranslated one."}}},"AccountImportItem":{"type":"object","description":"One row of the accounts file and everything that happened to it.","required":["row_number","status","errors","warnings","series"],"properties":{"row_number":{"type":"integer","description":"Row number **as the spreadsheet counts it**: the header is row 1, so the first account is row 2. Not an index — the point is that you can open the file and go to that line.","example":2},"external_ref":{"type":"string","nullable":true,"description":"Your own id for the account; `null` when the row was too malformed to read it."},"nif":{"type":"string","nullable":true,"description":"The row's tax id, normalised; `null` when it could not be read."},"status":{"$ref":"#/components/schemas/AccountImportItemStatus"},"live_activation_verdict":{"allOf":[{"$ref":"#/components/schemas/LiveActivationVerdict"}],"description":"Whether this row's NIF may be switched on in Live, decided exactly as `POST /v1/companies/{company_id}/activations` decides it. Anything other than `ENTITLED` blocks the row: the import never opens a checkout on your behalf. Absent when the row was rejected before reaching that question."},"account":{"allOf":[{"$ref":"#/components/schemas/ProvisionAccountResult"}],"description":"The account this row produced — the same shape `POST /v1/accounts` returns, carrying `account_id`, `company_id` and the single-use `claim_token` / `claim_url` to hand to the holder. Absent in a preview (nothing was provisioned) and on a `BLOCKED` or `ERROR` row. On `ALREADY_EXISTS` it is the account that was already there, and a claim link still valid is kept: `claim_token` is `null` and `claim_link_already_issued` is `true`. **The token is shown once**: store it, or re-issue it with `POST /v1/accounts/{account_id}/claim-tokens`."},"series":{"type":"array","description":"What each declared series did on this account. Empty when the import declares none.","items":{"$ref":"#/components/schemas/AccountImportSeriesOutcome"}},"customers":{"allOf":[{"$ref":"#/components/schemas/AccountImportCustomersOutcome"}],"description":"How the shared customers file landed on **this** account. Absent in a preview, and when no customers file was sent: whether a customer is new depends on the account, and that only shows up when the import runs."},"errors":{"type":"array","description":"What stops this row. A row with errors is never executed.","items":{"$ref":"#/components/schemas/AccountImportIssue"}},"warnings":{"type":"array","description":"What you should know even though the row goes through.","items":{"$ref":"#/components/schemas/AccountImportIssue"}}}},"AccountImportCustomerRow":{"type":"object","required":["row_number","errors"],"properties":{"row_number":{"type":"integer","description":"Row as the spreadsheet counts it (header is row 1)."},"nif":{"type":"string","nullable":true},"legal_name":{"type":"string","nullable":true},"errors":{"type":"array","items":{"$ref":"#/components/schemas/AccountImportIssue"}}}},"AccountImportCustomersSource":{"type":"object","description":"The shared customers file, checked once for the whole import — it is the same list for every account, so validating it per account would repeat the same census lookups N times over.\n\nDeliberately **not** the shape of `POST /v1/companies/{company_id}/customers/imports/preview`: that one reports duplicates against one company's customers, and here there is no single company to be duplicate of. What is reported here is only what makes a customer unusable everywhere — bad data, or a tax id the AEAT does not know.","required":["total_rows","valid","invalid","rejected"],"properties":{"total_rows":{"type":"integer","example":42},"valid":{"type":"integer","description":"Customers that will be applied to every account.","example":40},"invalid":{"type":"integer","description":"Customers that will be applied to none.","example":2},"rejected":{"type":"array","description":"The rejected rows, so you can fix the file. Valid rows are counted, not listed.","items":{"$ref":"#/components/schemas/AccountImportCustomerRow"}}}},"AccountImportStatistics":{"type":"object","description":"Aggregated outcome. Every field except the write counters classifies rows of the accounts file: each row falls into exactly one of `valid`, `with_warnings`, `already_existed`, `blocked` and `with_errors`, so those five add up to `total_rows`.\n\nThe write counters — `accounts_created`, `live_activations_created`, `series_created`, `customers_created` — are the only fields that count writes, so they are what to read to know whether an import worked. They are all `0` in a preview.","required":["total_rows","valid","with_warnings","already_existed","blocked","with_errors","importable","accounts_created","live_activations_created","live_activations_pending","series_created"],"properties":{"total_rows":{"type":"integer","example":19},"valid":{"type":"integer","example":11},"with_warnings":{"type":"integer","example":1},"already_existed":{"type":"integer","example":6},"blocked":{"type":"integer","example":1},"with_errors":{"type":"integer","example":0},"importable":{"type":"integer","description":"`valid + with_warnings` — rows a real import would provision.","example":12},"accounts_created":{"type":"integer","description":"Accounts actually provisioned by this request. Always `0` in a preview.","example":12},"live_activations_created":{"type":"integer","description":"NIFs actually switched on by this request. In Live each one adds an item to your subscription, so this is what was billed. Always `0` in a preview.","example":12},"live_activations_pending":{"type":"integer","description":"NIFs a real import **would** switch on. In a preview this is the billing forecast — read it before importing; in an import it is what is left, normally `0`.","example":0},"series_created":{"type":"integer","example":24},"customers_created":{"type":"integer","nullable":true,"description":"Customer rows created across all **managed accounts** of the file. `null` when no customers file was sent, and `0` in a preview. Your own company, when `options.apply_customers_to_own_company` asked for it, is counted apart in `own_company_customers` and never here.","example":24}}},"AccountImportResult":{"type":"object","description":"The outcome of the file, row by row, in one shape for the preview and for the import. Which one produced it is `metadata.is_dry_run`; what it actually wrote is the write counters in `statistics`.","required":["metadata","accounts_validation","statistics"],"properties":{"metadata":{"$ref":"#/components/schemas/AccountImportMetadata"},"accounts_validation":{"type":"array","description":"One entry per data row of the accounts file, in file order and **never filtered**: a row that failed keeps its place and its `row_number`, so the answer lines up with the spreadsheet.","items":{"$ref":"#/components/schemas/AccountImportItem"}},"customers_source":{"allOf":[{"$ref":"#/components/schemas/AccountImportCustomersSource"}],"description":"The optional customers file, checked **once** for the whole import. Absent when none was sent. What each account did with it is on its own row."},"own_company_customers":{"allOf":[{"$ref":"#/components/schemas/AccountImportCustomersOutcome"}],"description":"What the shared customers file did on **your own** company, when `options.apply_customers_to_own_company` asked for it. Absent otherwise. It sits at this level and not among the rows because your company is not a row of the accounts file: it is not provisioned, not activated and not given series — only its customers are seeded.\n\nSame shape as the per-account outcome, with one difference that `metadata.is_dry_run` settles: in a preview `created` is what **would** be created, since nothing was written; in an import it is what reached the database. `already_existed` is what the company already knew either way — which is what makes re-uploading the file cheap here too.\n\n**It is deliberately not added to `statistics.customers_created`.** That counter is about the accounts you imported, and folding your own copy into it would inflate the number you reconcile your onboarding against.\n\nYour own company also **fails like a row**: if seeding it fails, the managed accounts already seeded stay exactly as they are and the failure is reported here, with its rows counted in `failed`."},"statistics":{"$ref":"#/components/schemas/AccountImportStatistics"}}},"PayloadTooLargeDetails":{"type":"object","description":"Size information for a rejected payload. Byte counts are strings, not\nnumbers, to stay compatible with consumers of the pre-existing\n`FILE_TOO_LARGE` response.\n","required":["max_size_formatted"],"properties":{"max_size_bytes":{"type":"string","description":"Maximum size allowed for this request, in bytes. Absent when the\nlimit that was hit does not expose a numeric maximum.\n","example":"2097152"},"max_size_formatted":{"type":"string","description":"The same maximum, rendered for display.","example":"2.0 MB"},"request_size_bytes":{"type":"string","description":"Size the request declared in its `Content-Length` header. Present\nwhen the whole body was measured, absent when the limit was reached\nwhile reading an individual uploaded file.\n","example":"5242880"}}},"ExemptionReasonCatalogEntry":{"type":"object","required":["code","label","description","category","classification_type","available_as_default","available_on_invoice_line"],"properties":{"code":{"type":"string","description":"Enum value to send in invoice line exemption_reason field","example":"EXENTA_ART_20"},"label":{"type":"string","description":"i18n key for the label (resolve via translations)","example":"invoice.exemption.EXENTA_ART_20"},"description":{"type":"string","description":"i18n key for the description","example":"invoice.exemption.EXENTA_ART_20"},"category":{"type":"string","description":"Category grouping for UI display","example":"OPERACIONES EXENTAS"},"classification_type":{"type":"string","enum":["EXENTA","NO_SUJETA","NO_SUJETA_LOCALIZACION","SUJETA_NO_EXENTA_ISP","SUJETA_NO_EXENTA"],"description":"Tax classification type that determines VeriFactu behavior:\n- EXENTA: Exempt operation (E1-E6), no tax fields\n- NO_SUJETA: Not subject to tax (N1), no tax fields\n- NO_SUJETA_LOCALIZACION: Not subject by localization rules (N2), no tax fields\n- SUJETA_NO_EXENTA_ISP: Reverse charge (S2), tax rate forced to 0%\n- SUJETA_NO_EXENTA: Normal taxed (S1), requires tax rate\n"},"available_as_default":{"type":"boolean","description":"Whether a NIF can declare this reason as its **default exemption** in\n`PUT /v1/configuration/taxes`, or only per invoice line.\n\nOnly the reasons the issuer verifies on its own qualify (`EXENTA_ART_20` and\n`OTRO`). The ones depending on the recipient or the operation would assert a\nfact — printed as a legal mention on every PDF — that nobody checked, and the\n`REGIMEN_ART_*` ones are already derived from the stored regime key.\n","example":false},"available_on_invoice_line":{"type":"boolean","description":"Whether this reason can actually be sent in an invoice line's `exemption_reason`.\n\nSome reasons are published but not accepted: they are special-regime codes that do\nnot travel in VeriFactu's `operacion_exenta`, so the API rejects them on POST. They\nare still listed —**marked, not removed**— because this catalogue also translates\ncodes that are already stored on past invoices.\n\n**Which ones is deliberately not written here.** Read this flag per entry instead of\nhard-coding a list: it is derived from the same VeriFactu catalogue that decides the\nrejection, so it follows AEAT the day the set changes — a list copied into prose (or\ninto your code) would not.\n","example":true}}},"TaxTypesCatalog":{"type":"object","properties":{"tax_regimes":{"type":"array","items":{"$ref":"#/components/schemas/TaxRegime"}},"irpf_types":{"type":"array","items":{"$ref":"#/components/schemas/IrpfType"}},"equivalence_surcharges":{"type":"array","items":{"$ref":"#/components/schemas/EquivalenceSurcharge"}},"exemption_reasons":{"type":"array","items":{"$ref":"#/components/schemas/ExemptionReasonCatalogEntry"}}}},"TaxTypesCatalogResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TaxTypesCatalog"}}}]},"InvoiceTemplateType":{"type":"string","description":"Invoice PDF template. `MODERN_TABLE` is a structured table layout for\nproduct/service lines; `PROFESSIONAL_SERVICE` is a text-based layout.\n","enum":["MODERN_TABLE","PROFESSIONAL_SERVICE"],"example":"MODERN_TABLE"},"WithholdingOptions":{"type":"object","description":"Withholding rates a company can bear, deduced from the first letter of its NIF. Only\nindividuals pay IRPF (Law 35/2006, art. 8), so no entity bears the rates of individuals\n(1, 2, 7, 15, and 6, 2.8 and 7.6 in Ceuta and Melilla); any rate an entity cannot bear, on\nits invoices, products, default configuration or recurring templates, is rejected with\n`IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`.\n\n- **Legal person that pays Corporate Income Tax** (NIF starting with `A`, `B`, `C`, `D`,\n  `F`, `G`, `Q`, `R` or `U`) and **permanent establishment of a non-resident entity**\n  (`W`, which bears the Corporate Income Tax withholding regime, Royal Legislative Decree\n  5/2004, art. 23.1): only `0`, `19` and `24`. Its income bears withholding only when it\n  is one of those listed in the Corporate Income Tax regulation (Royal Decree 634/2015,\n  art. 60: rents of urban property, image rights, director fees, capital income…), at 19 %\n  or, for image rights, 24 % (art. 66); 19 % is halved to 9.5 for rents of urban property\n  in Ceuta and Melilla. An exempt entity bears none on its exempt income. The suggestion is\n  `0`: a provision of services is not in that list.\n- **Non-resident entity** (`N`): `0`, `19` and `24`, the Non-Residents Income Tax\n  withholding (Royal Legislative Decree 5/2004, arts. 25.1 a) and 31): 24 %, 19 % if it\n  resides in the European Union or the European Economic Area, 0 if the income is exempt\n  or a tax treaty says so. It depends on facts the NIF does not show: no suggestion.\n- **State, Autonomous Community or local entity** (`S`, `P`): fully exempt, its income\n  bears no withholding (Law 27/2014, art. 9.1; Royal Decree 634/2015, art. 61): only `0`.\n- **Individual, or entity whose income is attributed to its members** (`E`): every rate\n  of the IRPF regulation, including the Ceuta and Melilla reduced rates (6, 2.8, 7.6), but\n  not the Corporate Income Tax 9.5. Which one applies depends on the activity and its years, which\n  the NIF does not show, so there is no suggestion.\n- **Any other NIF** (`H`, `J`, `V`): not restricted, no suggestion.\n","required":["allowed_irpf_rates","suggested_irpf_rate"],"properties":{"allowed_irpf_rates":{"type":"array","description":"The rates this company can use, in ascending order.","items":{"$ref":"#/components/schemas/IrpfPercentage"},"example":[0,19,24]},"suggested_irpf_rate":{"type":"number","nullable":true,"description":"One of `allowed_irpf_rates`.\n\nThe rate to preselect for this company, or `null` when it depends on facts the NIF\ndoes not show and the user has to choose.\n","example":0}}},"DocumentTypeDefaultStatus":{"type":"object","required":["document_type","exists"],"description":"Status of the default invoice series for a single `DocumentType` in the\nrequest's environment (resolved via auth context, no input needed).\n\nWhen `exists` is `false`, `series_id` and `code` are absent. The integration\nsetup screen reads this list to surface missing defaults at config time\ninstead of failing webhooks at runtime with `MISSING_DEFAULT_SERIES`.\n","properties":{"document_type":{"$ref":"#/components/schemas/DocumentType"},"exists":{"type":"boolean","description":"Whether the user has a default series for this document type."},"series_id":{"type":"string","format":"uuid","nullable":true,"description":"ID of the configured default series (only when exists=true)."},"code":{"type":"string","nullable":true,"description":"Code of the configured default series (only when exists=true)."},"provisional":{"type":"boolean","description":"Whether the default series is still **provisional**: it exists but has not\nissued any invoice yet, so its numbering (`initial_number`) can still be\nchanged. The invoice creation screen uses this to warn that numbering will\nstart at 1 before it is too late — once the first invoice is issued the\nnumbering is frozen and `initial_number` can no longer be updated.\n\nAlways `false` when `exists` is `false`.\n"}}},"PatchSeriesRequest":{"type":"object","description":"Partial update of an invoice series (RFC 5789). Only the fields present in the\nbody are touched:\n\n- **field omitted** → the current value is kept;\n- **field sent with a value** → replaced;\n- **field sent as `null`** → cleared (only `description`, the one field a series\n  can live without).\n\nThe same rules as `PUT` apply: the fields that drive numbering (`code`, `format`,\n`counter_reset`, `initial_number`) and `document_type` are rejected once the series has\nissued invoices (`document_type` can still be set on an `UNASSIGNED` series), so renaming a\nseries in production keeps working.\nMoving a series to `document_type: UNASSIGNED` is rejected with\n`422 SERIES_UNASSIGNED_TYPE_NOT_ALLOWED`; a series that already is `UNASSIGNED` can be\nedited without changing its type.\n","properties":{"document_type":{"$ref":"#/components/schemas/DocumentType"},"name":{"type":"string","minLength":1,"maxLength":100,"example":"Main Series","description":"Descriptive name of the series. Cannot be cleared."},"code":{"$ref":"#/components/schemas/SeriesCode"},"description":{"type":"string","maxLength":1000,"nullable":true,"example":"Series for standard invoices","description":"Series description. Send `null` to clear it."},"format":{"$ref":"#/components/schemas/SeriesFormat"},"counter_reset":{"$ref":"#/components/schemas/CounterReset"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"example":54,"description":"Initial number for this series counter.\nOnly while the series has no issued invoices.\nIt applies only to the first period in which the series issues an invoice: with\n`counter_reset: ANNUAL` or `MONTHLY`, every later year or month starts at 1. With\n`NEVER` there is a single period, so numbering simply continues from it.\n"},"active":{"type":"boolean","description":"Whether the series is active.\n\n**Restriction:** A default series cannot be deactivated\n(another must be set as default first).\n"},"default_series":{"type":"boolean","description":"Whether this is the default series.\n\n**Restriction:** An inactive series cannot be marked as default.\n"}}},"CensusStatus":{"type":"string","description":"Census situation AEAT reported for a NIF.\n\n`status` tells you whether the NIF passed; this tells you *why*, and the\ndifference matters because each case is fixed differently:\n\n- `IDENTIFIED`: the taxpayer is identified with the data supplied.\n- `IDENTIFIED_DEREGISTERED`: identified, but deregistered in the tax\n  census. This is the AEAT census, not the scheduled shutdown of a NIF in\n  BeeL. Not valid: it cannot issue invoices, though it can still receive\n  them.\n- `IDENTIFIED_REVOKED`: identified, but its NIF was revoked by the AEAT and\n  published in the BOE. Not valid on either side — it cannot operate.\n- `NOT_IDENTIFIED`: no taxpayer matches the data supplied.\n- `NOT_IDENTIFIED_SIMILAR`: an individual whose name differs slightly from\n  the census one. Only individuals — a legal entity's name is **not\n  verified** at all, so it can never come back as similar.\n- `UNKNOWN`: AEAT answered something we do not recognise. Not a \"no\".\n","enum":["IDENTIFIED","IDENTIFIED_DEREGISTERED","IDENTIFIED_REVOKED","NOT_IDENTIFIED","NOT_IDENTIFIED_SIMILAR","UNKNOWN"],"example":"IDENTIFIED"},"CompanyInclude":{"type":"string","description":"Derived data to expand on each company of the list.","enum":["readiness"],"example":"readiness"},"AccountState":{"type":"string","description":"Account lifecycle stage, expressed in the same Test/Live vocabulary as `Environment`.\n`TEST_ONLY` means Live has not been activated for the account (no billing set up), so\nemission to the real AEAT is blocked; `PROD_ACTIVE` means it has.\n","enum":["TEST_ONLY","PROD_ACTIVE"],"example":"TEST_ONLY"},"IssuingReadinessData":{"type":"object","description":"Issuing-readiness of a company: whether it can issue its STANDARD invoice\nright now, in this environment, and — if not — exactly what is missing. `ready`\nis true only when `blockers` is empty. Issuing any fiscal document requires the\ncompany to be activated in that environment (`COMPANY_NOT_ACTIVATED` otherwise),\nwhether or not it goes to VeriFactu. The VeriFactu capability chain (census,\nsignature) is only demanded on top of that when the company is under the VeriFactu\nregime in this environment — the same fact that decides, at issue time, whether its\ninvoices are registered. With VeriFactu off, a NIF, a default series and a live\nactivation are enough to be ready.\n","properties":{"ready":{"type":"boolean","description":"true if the company can issue invoices (no blockers)."},"blockers":{"type":"array","description":"Reasons the company cannot issue yet (empty when ready). Each one names what is\nmissing and how to clear it:\n\n- `COMPANY_HAS_NO_NIF`: the company has no NIF. Set `nif` with\n  `PATCH /v1/companies/{company_id}`; once set it cannot be changed.\n- `SERIES_DEFAULT_NOT_FOUND`: there is no default `STANDARD` invoice series in this\n  environment. The `SIMPLIFIED` and `CORRECTIVE` ones are created on first use.\n  `PUT /v1/companies/{company_id}/series/defaults` creates the missing ones, or mark an\n  existing series with `PUT /v1/companies/{company_id}/series/{series_id}/default`.\n- `PROFILE_INCOMPLETE`: the fiscal identity is not complete enough to write an invoice\n  header: entity type, legal name or full fiscal address is missing. Complete\n  `entity_type`, `legal_name` and `address` with `PATCH /v1/companies/{company_id}`. A\n  missing NIF is reported as `COMPANY_HAS_NO_NIF` instead, never as both.\n- `COMPANY_NOT_ACTIVATED`: the company is not activated in the environment of the\n  request, and it is not under the VeriFactu regime. Activate it with\n  `POST /v1/companies/{company_id}/activations` for that environment.\n- `ENV_MISMATCH`: the same missing activation, for a company under the VeriFactu\n  regime. Activate it as above, or call with a credential of the environment where it\n  is already activated. The first stage of the VeriFactu chain.\n- `NIF_NOT_REGISTERED`: the NIF is not registered for VeriFactu submission in this\n  environment. Turning VeriFactu on\n  (`PUT /v1/companies/{company_id}/verifactu-configuration` with `enabled: true`)\n  registers it; in Live that call needs the signed representation first. Never reported\n  in Test, where the NIF is registered with its first submission.\n- `NIF_REPRESENTATION_REQUIRED`: the NIF is registered, but there is no signed AEAT\n  representation in force. Generate it with\n  `POST /v1/companies/{company_id}/representation`, sign it digitally and send it with\n  `POST /v1/companies/{company_id}/representation/submit`. Live only.\n","items":{"type":"string","enum":["COMPANY_HAS_NO_NIF","SERIES_DEFAULT_NOT_FOUND","PROFILE_INCOMPLETE","COMPANY_NOT_ACTIVATED","ENV_MISMATCH","NIF_NOT_REGISTERED","NIF_REPRESENTATION_REQUIRED"]}},"verifactu":{"type":"object","description":"The compliance question, separate from the operational `ready` above: would\nthis NIF pass a VeriFactu emission right now? Relevant before putting the company\nunder the VeriFactu regime. For a company already under the regime,\n`verifactu.ready` coincides with `ready` (same evaluation).\n","properties":{"ready":{"type":"boolean","description":"true only when the company is operationally `ready` AND its VeriFactu\ncapability chain is clean. An operational blocker (missing NIF, default\nseries, or an incomplete fiscal identity) makes this false even if the\nchain itself is complete — and, since those are not chain stages, it does\nso with an EMPTY `blockers` list here. The reason is in the top-level\n`blockers`, which is where operational blockers are reported.\n"},"blockers":{"type":"array","description":"ONLY the incremental VeriFactu capability stages, in order and mutually\nexclusive — one reason at a time, without repeating the operational\nblockers (NIF/series/fiscal identity) listed above. The stages, and how to\nclear each one, are the same as in the top-level `blockers`:\n\n- `ENV_MISMATCH`: the company is not activated in the environment of the request.\n- `NIF_NOT_REGISTERED`: the NIF is not registered for VeriFactu submission in\n  this environment; turning VeriFactu on registers it.\n- `NIF_REPRESENTATION_REQUIRED`: no signed AEAT representation in force. Only\n  appears where a signed representation is actually demanded (Live).\n","items":{"type":"string","enum":["ENV_MISMATCH","NIF_NOT_REGISTERED","NIF_REPRESENTATION_REQUIRED"]}}}}}},"CompanySeriesNumbering":{"type":"object","description":"How one of the series the company is born with should be seeded. All fields are\noptional and independent: omit one and it falls back to the system default. Same\nformat/reset compatibility rules and error codes as the parent block.\n","properties":{"code":{"$ref":"#/components/schemas/SeriesCode"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"example":40,"description":"Number this series' counter starts at, to continue the numbering already used\nelsewhere. Defaults to 1 when omitted. It applies only to the first period in which\nthe series issues an invoice; with an `ANNUAL` or `MONTHLY` reset every later period\nstarts at 1.\n"},"format":{"allOf":[{"$ref":"#/components/schemas/SeriesFormat"}],"description":"Format template this series' invoice numbers are printed with. Defaults to\n`{CODIGO}-{YYYY}-{NUM:4}` when omitted.\n"},"counter_reset":{"allOf":[{"$ref":"#/components/schemas/CounterReset"}],"description":"When this series' counter resets (`NEVER`/`ANNUAL`/`MONTHLY`). Defaults to\n`ANNUAL` when omitted — so a custom `format` without a year token must come\nwith `counter_reset: NEVER`.\n"}}},"CompanyNumbering":{"type":"object","description":"Configuration of the invoice series the company is born with. Optional and additive:\nomit it — or any field — and the system default applies for that field: series\n`F`/`S`/`R`, format `{CODIGO}-{YYYY}-{NUM:4}`, `ANNUAL` counter reset, starting at 1,\nexactly as before.\n\nSend it when the business already issued invoices with another system this year and\nwants to **continue** its numbering, or simply wants its series born with a specific\nshape — this is the only moment it can be expressed in the same call. Once a series\nissues its first invoice BeeL freezes its numbering: `PATCH\n/v1/companies/{company_id}/series/{series_id}` then rejects `initial_number` with\n`SERIES_INITIAL_NUMBER_LOCKED_HAS_INVOICES`.\n\nIt covers the **three** series a company is born with:\n\n* the **ordinary** one (real invoices) — the fields at this level, default `F`.\n* the **simplified** one (ticket-style invoices) — `simplified`, default `S`.\n* the **corrective** one (rectificativas) — `corrective`, default `R`.\n\nEach series takes `code`, `initial_number`, `format` and `counter_reset`, all\noptional and independent: omit a field and that series keeps the system default\nfor it.\n\n`format` and `counter_reset` must be able to tell reset periods apart, with the\nsame rules and error codes as `POST /v1/companies/{company_id}/series`: a `MONTHLY` reset\nrequires `{MM}` plus a year token in the format\n(`SERIES_MONTHLY_REQUIRES_MONTH_AND_YEAR`); an `ANNUAL` reset requires a year token\n(`SERIES_ANNUAL_REQUIRES_YEAR`). Mind the default reset is `ANNUAL`: a format\nwithout a year token (e.g. `{CODIGO}-{NUM:6}`) also needs `counter_reset: NEVER`\nin the same series block. Two series that could print the same number (for instance,\nthe same literal format with no `{CODIGO}`) are rejected with `409\nSERIES_FORMAT_OVERLAPS`, because an invoice number must be unique per issuer.\n\nThe list of series is **not** negotiable — a company always starts with exactly\nthese three, one default per document type, because a company without a default\nseries cannot issue at all (`NO_DEFAULT_SERIES`). You configure how each of them is\nborn, not which ones exist. More series can be added later with\n`POST /v1/companies/{company_id}/series`.\n\n**Per environment**: each activation is self-contained and seeds exactly what its\nrequest carries. Activating the same NIF in the other environment later does **not**\ncopy this configuration — repeat your `numbering` block in that activation call if\nyou want the same series there; without it the other environment gets the system\ndefaults.\n\nOnly valid when the request activates the company: with `activate: false` no series\nare seeded, so a `numbering` block that asks for anything is rejected with `422`\n`NUMBERING_REQUIRES_ACTIVATION` instead of being silently discarded.\n","properties":{"code":{"$ref":"#/components/schemas/SeriesCode"},"initial_number":{"type":"integer","format":"int64","minimum":1,"maximum":999999,"example":151,"description":"Number the ordinary series counter starts at. If the last invoice issued\nelsewhere was `2026-0150`, send `151`. Defaults to 1 when omitted. It applies only\nto the first period in which the series issues an invoice; with an `ANNUAL` or\n`MONTHLY` reset every later period starts at 1.\n"},"format":{"allOf":[{"$ref":"#/components/schemas/SeriesFormat"}],"description":"Format template the ordinary series' invoice numbers are printed with\n(`{CODIGO}`, `{YYYY}`/`{YY}`, `{MM}`, `{NUM}`/`{NUM:X}` — must contain `{NUM}`\nor `{NUM:X}`). Defaults to `{CODIGO}-{YYYY}-{NUM:4}` when omitted.\n"},"counter_reset":{"allOf":[{"$ref":"#/components/schemas/CounterReset"}],"description":"When the ordinary series' counter resets (`NEVER`/`ANNUAL`/`MONTHLY`).\nDefaults to `ANNUAL` when omitted — so a custom `format` without a year token\nmust come with `counter_reset: NEVER`.\n"},"simplified":{"$ref":"#/components/schemas/CompanySeriesNumbering"},"corrective":{"$ref":"#/components/schemas/CompanySeriesNumbering"}}},"PaymentRequiredResponse":{"description":"Returned with HTTP 402 when a production NIF cannot be created because\nthe account has no billing set up, or has an outstanding unpaid\npayment (retries exhausted). No company is created and **no checkout is ever opened here**:\na production charge hangs off activating a signalled company in Live.\n","allOf":[{"$ref":"#/components/schemas/ErrorResponse"}],"example":{"success":false,"error":{"code":"PAYMENT_REQUIRED","message":"You have an outstanding payment. Settle it before creating a production company."},"meta":{"timestamp":"2026-05-29T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}},"CompanyStatsData":{"type":"object","required":["company_id","invoice_count"],"properties":{"company_id":{"type":"string","format":"uuid"},"invoice_count":{"type":"integer","format":"int64","description":"Fiscal documents issued by this company. Drafts, scheduled invoices and proformas never count, because there is no fiscal document yet. A rectifying invoice counts as a document of its own. An invoice voided with `POST /v1/companies/{company_id}/invoices/{invoice_id}/void` (issued by mistake) or a simplified invoice replaced by a standard invoice does not count, and neither does a rectifying invoice of an invoice voided that way. An invoice voided by a total rectifying invoice keeps counting, together with that rectifying invoice, so the pair counts as two documents.\nNot monotonic: voiding an invoice with that endpoint lowers this count, and rectifying an invoice for its full amount raises it by one document. Do not use it as a synchronisation cursor; webhooks and `GET /v1/accounts/{account_id}/request-logs` are the surfaces for that.","example":42},"last_invoice_at":{"type":"string","format":"date-time","nullable":true,"description":"Issue date of the most recent document in the same set as `invoice_count` (ISO 8601), or null if none. This is the issue date, not the accrual date. It moves *backwards* when the most recent document stops counting; see `invoice_count`.","example":"2026-05-12T00:00:00Z"}}},"ListCompanyStats200Response":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["stats","pagination"],"properties":{"stats":{"type":"array","items":{"$ref":"#/components/schemas/CompanyStatsData"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"IssuingReadinessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"$ref":"#/components/schemas/IssuingReadinessData"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"BillingPeriod":{"type":"string","description":"Nominal billing period of a subscription.","enum":["MONTHLY","ANNUAL"],"example":"MONTHLY"},"ActivateCompanyRequest":{"type":"object","required":["environment"],"properties":{"environment":{"$ref":"#/components/schemas/Environment"},"success_url":{"type":"string","description":"Where Stripe returns after the card is captured. Only used when switching on in Live with no card on file. May embed Stripe's `{CHECKOUT_SESSION_ID}` template, which is why it is a plain string and not a `uri`: the braces are not legal URI characters.","example":"https://your-app.example.com/billing/return?session={CHECKOUT_SESSION_ID}"},"cancel_url":{"type":"string","format":"uri","description":"Where Stripe returns if the checkout is abandoned.","example":"https://your-app.example.com/billing"},"billing_period":{"allOf":[{"$ref":"#/components/schemas/BillingPeriod"}],"description":"Billing period of the plan bought when switching on in Live opens a checkout. `ANNUAL` charges the yearly per-NIF price up front, with no overage item in the checkout; `MONTHLY` is the monthly per-NIF price plus overage. Only read when a checkout is opened. Defaults to `MONTHLY` when omitted."}}},"CompanyActivation":{"type":"object","required":["company_id","environment","already_active","scheduled_deactivation_cancelled"],"properties":{"company_id":{"type":"string","format":"uuid"},"environment":{"$ref":"#/components/schemas/Environment"},"already_active":{"type":"boolean","description":"The company was already on in that mode and nothing was written."},"scheduled_deactivation_cancelled":{"type":"boolean","description":"The call removed a pending switch-off. Comes with `already_active: true`, since the activation never stopped being live. Nothing was charged or credited."}}},"CompanyActivationResponse":{"allOf":[{"$ref":"#/components/schemas/SuccessResponse"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CompanyActivation"}}}]},"CompanyDeactivation":{"type":"object","required":["company_id","environment","already_scheduled"],"properties":{"company_id":{"type":"string","format":"uuid"},"environment":{"$ref":"#/components/schemas/Environment"},"effective_at":{"type":"string","format":"date-time","nullable":true,"description":"When the switch-off takes effect. Absent when immediate (Test, or Live under an enterprise contract). Until then the NIF still invoices and is not released."},"already_scheduled":{"type":"boolean","description":"The switch-off was already pending; the same date is returned."}}},"ConnectionStatus":{"type":"string","enum":["PENDING","ACTIVE","DISCONNECTED","ERROR"],"description":"Status of the payment provider connection.\n\n- `PENDING`: authorization opened, not completed yet\n- `ACTIVE`: the connection invoices incoming charges\n- `DISCONNECTED`: withdrawn — it no longer invoices anything\n- `ERROR`: the provider rejected the last call, so it needs reconnecting\n"},"ManagedPaymentConnection":{"type":"object","description":"A payment provider connection of a NIF (curated subset).","required":["id","provider","external_account_id","environment","status"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier of the connection. It is what addresses the connection everywhere\nelse — `PATCH`, `DELETE` and events all take it as `{connection_id}`.\n","example":"5f2b1c0e-9a3d-4c11-8f6e-7b0d2a4c9e13"},"provider":{"type":"string","description":"Payment provider slug (lowercase).","example":"stripe"},"external_account_id":{"type":"string","description":"Provider-side account id (e.g. Stripe `acct_...`).","example":"acct_1234567890"},"external_account_name":{"type":"string","nullable":true,"description":"Provider-side account display name, if any."},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"description":"Mode the connection lives in (`beel_sk_test_*` → `TEST`, `beel_sk_live_*` → `PROD`).\nTest and Live connections are independent, so the same provider account may appear in\nboth modes; this field tells them apart without inferring it from the key you asked\nwith.\n"},"status":{"$ref":"#/components/schemas/ConnectionStatus"},"connected_at":{"type":"string","format":"date-time","nullable":true,"description":"When the connection was established."}}},"ListManagedPaymentConnectionsResponse":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","required":["connections"],"properties":{"connections":{"type":"array","items":{"$ref":"#/components/schemas/ManagedPaymentConnection"}}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"PaymentConnectionEventSource":{"type":"string","enum":["PAYMENTS","INVOICES","BOTH"],"description":"Which family of Stripe events the connection invoices. One webhook endpoint receives\neverything the connected account emits; whatever falls outside the declared family is\nrecorded as skipped, deliberately ignored, and needs no attention.\n\n- `PAYMENTS`: charges, through `payment_intent.*` and `checkout.session.*`.\n- `INVOICES`: Stripe's own billing documents, through `invoice.*`.\n- `BOTH`: both families, deduplicated to a single invoice per charge.\n\nRefunds, disputes and credit notes are acted on under every value, because Stripe does not\nalways report a correction through the family the charge came in. A correction whose charge\nwas never invoiced is recorded as skipped and needs no action.\n"},"UpdateCompanyPaymentConnectionSeries":{"type":"object","description":"Series to number auto-issued invoices in. Omit a field to keep it, send `null` to clear it\nand fall back to the company default series for that document type.\n","properties":{"ordinaria":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued ordinary invoices."},"simplificada":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued simplified invoices."},"rectificativa":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued corrective invoices (refunds)."}}},"PaymentEventCategory":{"type":"string","description":"Which product line a payment event's money came from. A different axis from `event_kind`,\nwhich says what happened: a `checkout.session.completed` is `PAYMENT_COMPLETED` either way,\nand the category tells you whether it was a one-off checkout or the start of a subscription.\n\n- `DIRECT_PAYMENT`: a one-off charge\n- `SUBSCRIPTION`: a charge produced by a recurring subscription\n- `REFUND`: money given back\n- `CREDIT_NOTE`: a credit note issued on the provider's side\n","enum":["DIRECT_PAYMENT","SUBSCRIPTION","REFUND","CREDIT_NOTE"]},"CompanyPaymentConnectionFilters":{"type":"object","description":"Rules that decide which incoming charges reach auto-invoicing. Every axis is opt-in: a\nfield left out does not filter anything. A charge filtered out produces no invoice and is\nrecorded as skipped.\n","properties":{"min_amount":{"type":"number","format":"double","minimum":0,"description":"Charges below this amount are skipped.","example":10},"max_amount":{"type":"number","format":"double","minimum":0,"description":"Charges above this amount are skipped.","example":5000},"only_mapped_customers":{"type":"boolean","description":"When true, only charges from customers already mapped to a BeeL customer are invoiced.\n"},"allowed_customer_ids":{"type":"array","items":{"type":"string"},"description":"Provider-side customer ids (e.g. Stripe `cus_...`) allowed through. When set, no other\ncustomer is invoiced.\n","example":["cus_abc123","cus_def456"]},"excluded_description_patterns":{"type":"array","items":{"type":"string"},"description":"Regular expressions matched against the charge description. A charge matching any of\nthem is skipped.\n","example":[".*donation.*",".*tip.*"]},"required_description_patterns":{"type":"array","items":{"type":"string"},"description":"Regular expressions matched against the charge description. A charge must match at\nleast one of them to be invoiced.\n","example":[".*subscription.*"]},"disabled_categories":{"type":"array","items":{"$ref":"#/components/schemas/PaymentEventCategory"},"description":"Charge categories opted out of auto-invoicing. An empty list, or the field left out,\nleaves every category active.\n","example":["SUBSCRIPTION"]}}},"CompanyPaymentConnectionSeries":{"type":"object","description":"Invoice series the connection numbers its auto-issued invoices in, one per document type.\nA series left out falls back to the company default series for that type.\n","properties":{"ordinaria":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued ordinary invoices."},"simplificada":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued simplified invoices."},"rectificativa":{"type":"string","format":"uuid","nullable":true,"description":"Series for auto-issued corrective invoices (refunds)."}}},"PaymentEventStatus":{"type":"string","enum":["RECEIVED","PROCESSING","PROCESSED","FAILED","SKIPPED","MANUALLY_RESOLVED"],"description":"Processing status of a payment event.\n\n- RECEIVED: queued, awaiting processing\n- PROCESSING: in flight\n- PROCESSED: invoice was created (`invoice_id` is set)\n- FAILED: processing failed (see failure_reason / failure_message)\n- SKIPPED: filtered out by user rules (see skip_reason)\n- MANUALLY_RESOLVED: operator marked it as resolved outside BeeL — terminal\n"},"PaymentEventFailureCategory":{"type":"string","enum":["USER_CONFIGURATION","PROVIDER_DATA","DOMAIN_RULE","INFRASTRUCTURE","SYSTEM_ERROR"],"description":"High-level cause of an event that did not complete, so you can tell apart what you can fix\nfrom what you cannot.\n\n- `USER_CONFIGURATION`: your configuration stopped it (filters, missing series)\n- `PROVIDER_DATA`: the provider data was insufficient or inconsistent\n- `DOMAIN_RULE`: an invoicing rule forbade it — permanent, retrying changes nothing\n- `INFRASTRUCTURE`: a transient failure; retrying may work\n- `SYSTEM_ERROR`: a defect on our side\n"},"PaymentEventType":{"type":"string","enum":["PAYMENT_COMPLETED","PAYMENT_FAILED","REFUND_COMPLETED","DISPUTE_OPENED","DISPUTE_CLOSED","UNKNOWN"],"description":"What a payment event is about, once the provider's own event name has been classified.\n\n- `PAYMENT_COMPLETED`: a charge went through\n- `PAYMENT_FAILED`: a charge was attempted and did not go through\n- `REFUND_COMPLETED`: money was returned (refund or credit note)\n- `DISPUTE_OPENED`: the payer disputed a charge\n- `DISPUTE_CLOSED`: a dispute was settled, in favour of either party\n- `UNKNOWN`: the provider event does not fall in any of the above\n"},"PaymentEventFailureReasonCount":{"type":"object","description":"How many events share one failure reason.","required":["code","label","count"],"properties":{"code":{"type":"string","description":"Stable identifier of the reason. Branch on this, not on `label`.","example":"MISSING_DEFAULT_SERIES"},"label":{"type":"string","description":"The reason written out for a person, in the language of the request.","example":"No hay serie predeterminada configurada"},"count":{"type":"integer","description":"Events of the connection with this reason.","example":4}}},"PaymentEventCounts":{"type":"object","description":"How the events of the connection break down, over the whole connection and not just the\npage you are reading. The filters of the request (`q`, `status`, `needs_action`, dates,\namounts and the rest) do not narrow these counts, which may therefore exceed\n`pagination.total_items`: they are what lets you show \"12 need action\" without asking for\nevery page. Only two parameters have a bearing on them: `charges_only` changes what is\ncounted (money movements instead of events), and `include_discarded` decides whether\ndiscarded events take part in `by_status` and `failure_reasons`.\n","required":["total","discarded","needs_action","ignored","by_status","by_failure_reason","failure_reasons"],"properties":{"total":{"type":"integer","description":"Events of the connection that are not discarded — the size of the list you get when\nyou filter by nothing.\n","example":183},"discarded":{"type":"integer","description":"Events you discarded. They are not part of `total`; ask for them with\n`include_discarded=true`.\n","example":7},"needs_action":{"type":"integer","deprecated":true,"description":"Events that are waiting for you to do something — the same set you get with\n`needs_action=true`. Counts only events that are not discarded, so it is part of\n`total`.\n\n**Deprecated. Retires on 11 December 2026.** Filter with `needs_action=true` and read\n`pagination.total_items` instead. See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","example":12},"ignored":{"type":"integer","deprecated":true,"description":"Events that were skipped on purpose and need nothing from you (duplicates, your own\nfilters, event types that are not invoiced). Counts only events that are not\ndiscarded, so it is part of `total`.\n\n**Deprecated. Retires on 11 December 2026.** The closest surviving figure is\n`by_status.SKIPPED`. See the [migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","example":31},"by_status":{"type":"object","additionalProperties":{"type":"integer"},"description":"Number of events per processing status.","example":{"PROCESSED":148,"SKIPPED":31,"FAILED":4}},"by_failure_reason":{"type":"object","additionalProperties":{"type":"integer"},"deprecated":true,"description":"Number of events per failure reason, among those that did not complete. Events that\ncompleted do not appear here.\n\n**Deprecated. Retires on 11 December 2026.** Use `failure_reasons`, which carries the\nsame counts plus the reason written out for a person. See the\n[migration guide](https://docs.beel.es/changelog/payments-cleanup).\n","example":{"EVENT_IGNORED_BY_DESIGN":25,"MISSING_DEFAULT_SERIES":4}},"failure_reasons":{"type":"array","description":"Number of events per failure reason, among those that did not complete, with the\nreason already written out in the language of the request. Events that completed do\nnot appear here. Replaces the deprecated `by_failure_reason`.\n","items":{"$ref":"#/components/schemas/PaymentEventFailureReasonCount"}}}},"WebhookAccountRelationship":{"type":"string","enum":["own","managed","all"],"description":"Which accounts a subscription receives events from — the same vocabulary as the\n`account_relationship` field in every delivered event envelope. One term to ask for\nevents, the same term to route them on arrival.\n\n* `own` (default) — only your own account.\n* `managed` — only accounts you manage (accounts you provisioned). Whether you actually\n  receive them also depends on the management relationship granting data visibility; a\n  billing-only relationship does not.\n* `all` — both.\n\nA delivered event is always `own` or `managed` (never `all`): its\n`account_relationship`, alongside `account_id` and `account_external_ref`, tells you\nwhich account it belongs to.\n"},"WebhookDeactivationCause":{"type":"string","enum":["owner","beel"],"nullable":true,"description":"Who turned a subscription off, alongside `active: false`.\n\n* `owner` — you did, through the API or the dashboard. Turn it back on whenever you want.\n* `beel` — we did, because the endpoint stopped accepting anything at all for days.\n  Reactivating requires the endpoint to pass a test delivery first (`POST\n  /v1/webhooks/{webhook_id}/test`), so you unblock yourself without opening a ticket.\n"},"WebhookFailureCause":{"type":"string","nullable":true,"enum":["dns","connection","tls","timeout","http_client_error","http_server_error","invalid_url","forbidden_target","unknown"],"description":"Why a delivery to your endpoint did not succeed. The `error` field is free text meant for\na human reading a log; this field is the machine-readable counterpart, so a change in\nwording never changes your branching.\n\n* `dns` — the endpoint's domain does not resolve.\n* `connection` — the connection could not be opened (refused, host unreachable).\n* `tls` — the endpoint's TLS certificate is not valid or not trusted.\n* `timeout` — the endpoint accepted the connection but did not answer in time.\n* `http_client_error` — the endpoint answered 4xx.\n* `http_server_error` — the endpoint answered 5xx.\n* `invalid_url` — the subscription URL is malformed and has no host to deliver to.\n* `forbidden_target` — the URL points to a non-public network address, which we never\n  deliver to.\n* `unknown` — the failure could not be classified.\n\nNew values may be added over time. Treat any value you do not recognise as `unknown`.\n"},"WebhookSubscription":{"type":"object","required":["id","url","events","active","created_at"],"properties":{"id":{"type":"string","format":"uuid"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean","description":"Whether the subscription receives events. Turning it off, or BeeL. pausing it, drops the deliveries still waiting for an automatic retry: they are not sent when it is turned back on, and stay in the delivery log for a manual retry.\n"},"account_relationship":{"allOf":[{"$ref":"#/components/schemas/WebhookAccountRelationship"}],"description":"Which accounts this subscription receives events from. Same field name and values as the `account_relationship` carried by every event envelope.\n"},"deactivated_by":{"allOf":[{"$ref":"#/components/schemas/WebhookDeactivationCause"}],"description":"Who turned this subscription off. Absent while it is active. `beel` means we paused it because the endpoint went 25 consecutive deliveries without a single success over more than 48 hours — reactivating it requires a successful test delivery first.\n"},"deactivated_at":{"type":"string","format":"date-time","nullable":true,"description":"When the subscription was turned off. Null while it is active."},"last_error":{"type":"string","nullable":true,"description":"Last error seen in the current run of failed deliveries — what to fix. Cleared by the first successful delivery, together with the failure count.\n"},"last_error_cause":{"allOf":[{"$ref":"#/components/schemas/WebhookFailureCause"}],"description":"Stable code for `last_error`. Cleared alongside it by the first successful delivery.\n"},"consecutive_failures":{"type":"integer","description":"Deliveries in a row that never got through. A delivery that exhausted its attempts counts as one. Any successful delivery resets it to zero, so a healthy endpoint with occasional blips stays near zero.\n","example":0},"last_used_at":{"type":"string","format":"date-time","nullable":true,"description":"Last delivery that succeeded. Null if none ever has."},"created_at":{"type":"string","format":"date-time"}}},"WebhookEventTypeEnum":{"type":"string","description":"Available webhook event types:\n- `invoice.issued` — Invoice issued and finalized\n- `invoice.email.sent` — Invoice sent by email\n- `invoice.pdf.generated` — The PDF of an invoice was (re)rendered, so any copy you cached\n  is stale; fetch it again from the PDF endpoint\n- `invoice.voided` — Invoice voided\n- `recurring_invoice.paused` — A schedule stopped generating on its own (downgrade or a\n  permanent generation failure); the invoice it was going to issue will not arrive\n- `invoice.schedule_failed` — A scheduled invoice could not be issued on its date and BeeL.\n  gave up retrying; it stays in drafts and can be issued by hand once the cause is fixed\n- `verifactu.status.updated` — VeriFactu public status changed (see `VeriFactuSubmissionStatus`)\n- `account.claimed` — A provisioned account was claimed by its holder\n- `company.created` — A company was created inside a provisioned account\n- `representation.signed` — Fiscal representation signed for a NIF (production invoicing enabled)\n\nThe `account.*` events are delivered only to the **provisioner** that created the\naccount; a subscription on any other account never receives them.\n","enum":["invoice.issued","invoice.email.sent","invoice.pdf.generated","invoice.voided","recurring_invoice.paused","invoice.schedule_failed","verifactu.status.updated","account.claimed","company.created","representation.signed"]},"CreateWebhookSubscriptionRequest":{"type":"object","required":["url","events"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS endpoint URL that will receive webhook POST requests.","example":"https://yourapp.com/webhooks/beel"},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventTypeEnum"},"description":"List of event types to subscribe to.","example":["invoice.issued","verifactu.status.updated"]},"account_relationship":{"allOf":[{"$ref":"#/components/schemas/WebhookAccountRelationship"}],"default":"own","description":"Which accounts this subscription receives events from. Defaults to `own`. Same field name and values as the `account_relationship` carried by every event envelope.\n"}}},"WebhookTestResult":{"type":"object","properties":{"delivery_success":{"type":"boolean","description":"Whether your endpoint responded with HTTP 2xx."},"http_status":{"type":"integer","nullable":true,"description":"HTTP status code returned by your endpoint. Null if the connection failed."},"duration_ms":{"type":"integer","description":"Round-trip time in milliseconds."},"error":{"type":"string","nullable":true,"description":"Error message if the delivery failed (timeout, DNS, etc.)."},"failure_cause":{"allOf":[{"$ref":"#/components/schemas/WebhookFailureCause"}],"description":"Why the delivery failed, as a stable code. Absent when `delivery_success` is true. Branch on this, never on the wording of `error`.\n"}}},"WebhookSubscriptionWithSecret":{"allOf":[{"$ref":"#/components/schemas/WebhookSubscription"},{"type":"object","properties":{"secret":{"type":"string","description":"HMAC-SHA256 signing secret. **Only returned on creation and secret rotation.**\nStore it securely — it cannot be retrieved again.\n","example":"whsec_a3f5b2e1c9d8f7e6b5a4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2"},"test_delivery":{"allOf":[{"$ref":"#/components/schemas/WebhookTestResult"}],"nullable":true,"description":"Outcome of a one-off signed test delivery sent to your URL as part of creating the\nsubscription, so you learn whether your endpoint answers without a second call.\n\nIt is **best effort and never blocks the subscription**: the subscription in `data`\nexists and is active regardless of what this says, and the field is `null` when the\ntest could not be run at all. A failure here means your endpoint is not ready yet —\ntypically because you registered before deploying it — not that anything went wrong\nwith the registration. Only present on creation, never on secret rotation.\n"}}}]},"WebhookEventAccountRelationship":{"type":"string","enum":["own","managed"],"nullable":true,"description":"How `account_id` relates to you: `own` when the event happened in your own account,\n`managed` when it happened in an account you manage. `null` when the event is not\nscoped to an account.\n","example":"managed"},"WebhookEventDataInvoiceIssued":{"type":"object","description":"Data payload for `invoice.issued` events.","additionalProperties":false,"required":["invoice_id","invoice_number"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","example":"2025/0001"},"customer_email":{"type":"string","nullable":true},"customer_name":{"type":"string","nullable":true}}},"WebhookEventDataInvoiceEmailSent":{"type":"object","description":"Data payload for `invoice.email.sent` events.","additionalProperties":false,"required":["invoice_id","all_recipients","sent_at"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","nullable":true,"example":"2025/0001"},"all_recipients":{"type":"array","items":{"type":"string"},"description":"All email recipients (TO + CC)."},"sent_at":{"type":"string","format":"date-time"}}},"WebhookEventDataInvoicePdfGenerated":{"type":"object","description":"Data payload for `invoice.pdf.generated` events. Just the identity of the invoice: the\ndocument itself is fetched from the PDF endpoint, which mints a fresh pre-signed URL. One\ntravelling here would expire in five minutes — less than this delivery's own retry window.\n","additionalProperties":false,"required":["invoice_id"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","nullable":true,"description":"Null while the document has no number yet (a proforma, a draft).","example":"2025/0001"}}},"WebhookEventDataInvoiceVoided":{"type":"object","description":"Data payload for `invoice.voided` events.","additionalProperties":false,"required":["invoice_id","invoice_number"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","example":"2025/0001"},"cancellation_reason":{"type":"string","nullable":true}}},"WebhookEventDataRecurringInvoicePaused":{"type":"object","description":"Data payload for `recurring_invoice.paused` events. Only automatic pauses are delivered:\na schedule someone paused on purpose is not news to whoever paused it.\n","additionalProperties":false,"required":["recurring_invoice_id","reason","since"],"properties":{"recurring_invoice_id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true,"example":"Cuota mensual mantenimiento"},"reason":{"type":"string","enum":["DOWNGRADE","GENERATION_FAILURE"]},"blocker":{"type":"string","nullable":true,"description":"What blocked the emission, in the same vocabulary the emission API returns (the\n`blockers[]` of a 422 `EMISSION_NOT_READY`). Only for `GENERATION_FAILURE`.\n","example":"NIF_NOT_REGISTERED"},"since":{"type":"string","format":"date-time"}}},"WebhookEventDataInvoiceScheduleFailed":{"type":"object","description":"Data payload for `invoice.schedule_failed` events. The invoice is not lost: it stays in\ndrafts, untouched, and can be issued by hand once the cause is fixed.\n","additionalProperties":false,"required":["invoice_id"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","nullable":true,"description":"Usually `null`: the number is assigned on emission, and this invoice was never emitted.\n","example":"2026/0042"},"customer_name":{"type":"string","nullable":true,"description":"Legal name of the recipient — what identifies a draft that has no number yet.","example":"Talleres Pérez SL"},"scheduled_for":{"type":"string","format":"date","nullable":true,"description":"The date the invoice was due to be issued.","example":"2026-03-07"},"blocker":{"type":"string","nullable":true,"description":"What blocked the emission, in the same vocabulary the emission API returns (the\n`blockers[]` of a 422 `EMISSION_NOT_READY`). It may be absent when the permanent\nfailure was not a readiness one.\n","example":"NIF_NOT_REGISTERED"}}},"WebhookEventDataVeriFactuStatusUpdated":{"type":"object","description":"Data payload for `verifactu.status.updated` events.","additionalProperties":false,"required":["invoice_id","verifactu_registration_id","operation","new_status"],"properties":{"invoice_id":{"type":"string","format":"uuid"},"invoice_number":{"type":"string","nullable":true,"example":"2025-001"},"verifactu_registration_id":{"type":"string","format":"uuid"},"operation":{"allOf":[{"$ref":"#/components/schemas/VeriFactuRecordOperation"}],"description":"Which record changed status: the registration (`REGISTRATION`) or the cancellation of a\nvoided invoice (`VOID`). It is the record `verifactu_registration_id` identifies, the\nsame one `verifactu-records` lists with that `operation`.\n"},"previous_status":{"allOf":[{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"}],"nullable":true,"description":"Status of the invoice's record of this `operation` just before this change, whether\nthat was the same record or the one it replaces (a submission sent again after a\nrejection, a cancellation sent again). Omitted only when the invoice had no earlier\nrecord of this `operation`: the first notification of each operation arrives without\nit, so the first notification of a cancellation has no `previous_status` even though\nthe invoice already had a registration.\n"},"new_status":{"$ref":"#/components/schemas/VeriFactuSubmissionStatus"},"qr_url":{"type":"string","nullable":true,"description":"AEAT verification URL encoded in the invoice QR code. Set when the registration is\nsubmitted, so it is carried by every later status change, `REJECTED` included.\n","example":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=2025-001&fecha=07-03-2026&importe=121.00"},"qr_base64":{"type":"string","nullable":true,"description":"QR code as base64-encoded PNG for embedding in PDFs. Set when the registration is\nsubmitted, so it does not wait for `ACCEPTED`.\n"},"invoice_hash":{"type":"string","nullable":true,"description":"SHA-256 hash of the registration record, as VeriFactu defines it. Set when the\nregistration is submitted.\n","example":"B11F3A015173AD99075E2720F61E2DE1FF08CBFEDD85C6F73C77AD835301B2A3"},"error_code":{"$ref":"#/components/schemas/VeriFactuErrorCode"},"error_message":{"type":"string","nullable":true,"description":"Human-readable reason for the outcome. When the AEAT reported a remark or an error, it is\nthe AEAT's own description. When BeeL. decided the outcome (the submission was rejected\nbefore reaching the AEAT, or BeeL. stopped waiting for a final answer), it is a message\nwritten by BeeL., in English.\n"}}},"WebhookEventDataAccountClaimed":{"type":"object","description":"Data payload for `account.claimed` events. Delivered only to the provisioner\nthat created the account. Fetch the authoritative state by `account_id`.\n","additionalProperties":false,"required":["account_id","external_ref"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The provisioned account this event refers to."},"external_ref":{"type":"string","description":"Your own identifier for the account, to reconcile against your system."},"email":{"type":"string","format":"email","nullable":true,"description":"Email of the account holder who claimed the account."}}},"WebhookEventDataCompanyCreated":{"type":"object","description":"Data payload for `company.created` events. Delivered only to the provisioner\nthat created the account.\n","additionalProperties":false,"required":["account_id","external_ref","nif"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The provisioned account this event refers to."},"external_ref":{"type":"string","description":"Your own identifier for the account, to reconcile against your system."},"nif":{"type":"string","description":"The NIF (tax id) the holder registered."},"company_id":{"type":"string","format":"uuid","nullable":true,"description":"Unique identifier (UUID) of the created company."},"legal_name":{"type":"string","nullable":true,"description":"Registered legal/fiscal name."}}},"WebhookEventDataRepresentationSigned":{"type":"object","description":"Data payload for `representation.signed` events. Delivered only to the provisioner\nthat created the account.\n","additionalProperties":false,"required":["account_id","external_ref","company_id","nif","signed_at"],"properties":{"account_id":{"type":"string","format":"uuid","description":"The provisioned account this event refers to."},"external_ref":{"type":"string","description":"Your own identifier for the account, to reconcile against your system."},"company_id":{"type":"string","format":"uuid","description":"Unique identifier (UUID) of the company whose fiscal representation was signed."},"nif":{"type":"string","description":"The NIF whose fiscal representation was signed."},"signed_at":{"type":"string","format":"date-time","description":"When the fiscal representation was signed (enabling production invoicing for this NIF)."}}},"WebhookEvent":{"type":"object","description":"Payload delivered to your endpoint on every webhook event.","required":["id","type","created_at","api_version","livemode","data"],"properties":{"id":{"type":"string","description":"Unique webhook event ID (UUID).","example":"3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c"},"type":{"$ref":"#/components/schemas/WebhookEventTypeEnum"},"created_at":{"type":"string","format":"date-time"},"api_version":{"type":"string","example":"2025-01"},"livemode":{"type":"boolean","deprecated":true,"description":"Deprecated in favour of the `TEST`/`PROD` environment vocabulary: `livemode: true` is equivalent to environment `PROD`."},"test":{"type":"boolean","nullable":true,"description":"`true` only for test deliveries triggered from the dashboard."},"company_id":{"type":"string","format":"uuid","nullable":true,"description":"Unique identifier (UUID) of the company the event is about, or `null` when the event is\nnot scoped to a specific company. A single endpoint receives events for every company it\nmanages; route on this field.\n","example":"9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f"},"nif":{"type":"string","nullable":true,"description":"NIF of the company the event is about.","example":"B12345674"},"account_id":{"type":"string","format":"uuid","nullable":true,"description":"Account the event happened in. For your own events this is your account; for\nevents of accounts you manage it identifies which one.\n","example":"4d6d8dca-689a-44f4-bafc-88fa8a1bc34b"},"account_external_ref":{"type":"string","nullable":true,"description":"Your own identifier for that account, as supplied when you provisioned it\n(`external_ref`). `null` for accounts you did not provision.\n","example":"acct-2041"},"account_relationship":{"$ref":"#/components/schemas/WebhookEventAccountRelationship"},"data":{"oneOf":[{"$ref":"#/components/schemas/WebhookEventDataInvoiceIssued"},{"$ref":"#/components/schemas/WebhookEventDataInvoiceEmailSent"},{"$ref":"#/components/schemas/WebhookEventDataInvoicePdfGenerated"},{"$ref":"#/components/schemas/WebhookEventDataInvoiceVoided"},{"$ref":"#/components/schemas/WebhookEventDataRecurringInvoicePaused"},{"$ref":"#/components/schemas/WebhookEventDataInvoiceScheduleFailed"},{"$ref":"#/components/schemas/WebhookEventDataVeriFactuStatusUpdated"},{"$ref":"#/components/schemas/WebhookEventDataAccountClaimed"},{"$ref":"#/components/schemas/WebhookEventDataCompanyCreated"},{"$ref":"#/components/schemas/WebhookEventDataRepresentationSigned"}]}}},"UpdateWebhookSubscriptionRequest":{"type":"object","properties":{"url":{"type":"string","format":"uri","nullable":true,"description":"New HTTPS endpoint URL.","example":"https://yourapp.com/webhooks/beel"},"events":{"type":"array","nullable":true,"minItems":1,"items":{"$ref":"#/components/schemas/WebhookEventTypeEnum"},"description":"New list of event types to subscribe to."},"active":{"type":"boolean","nullable":true,"description":"Enable or disable the webhook subscription."},"account_relationship":{"allOf":[{"$ref":"#/components/schemas/WebhookAccountRelationship"}],"nullable":true,"description":"New set of accounts this subscription receives events from. Same field name and values as the `account_relationship` carried by every event envelope.\n"}}},"WebhookDeliveryLog":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"subscription_id":{"type":"string","format":"uuid"},"webhook_event_id":{"type":"string","format":"uuid","description":"Groups all delivery attempts for the same logical event."},"event_type":{"type":"string","example":"verifactu.status.updated"},"attempt_number":{"type":"integer","description":"Sequential number of this POST within its delivery. Manual retries continue the same sequence, so this can exceed the number of automatic attempts."},"http_status":{"type":"integer","nullable":true,"description":"HTTP response status code. Null if connection failed.","example":200},"response_body":{"type":"string","nullable":true,"description":"Body returned by your endpoint, kept for troubleshooting. Stored up to 2048 characters; longer responses are cut and `response_body_truncated` is true."},"response_body_truncated":{"type":"boolean","description":"True when your endpoint returned more than 2048 characters and `response_body` holds only the beginning of it."},"response_body_length":{"type":"integer","nullable":true,"description":"Length of the body your endpoint returned, before it was cut. Null for deliveries recorded before this field existed.","example":29094},"duration_ms":{"type":"integer","nullable":true,"description":"Delivery duration in milliseconds."},"success":{"type":"boolean"},"error_message":{"type":"string","nullable":true,"description":"Error description for failed deliveries (timeout, connection error, etc.)"},"payload":{"type":"string","nullable":true,"description":"The JSON payload that was sent (or attempted) to your endpoint."},"request_headers":{"type":"object","nullable":true,"description":"HTTP headers sent to your endpoint with this delivery attempt.\nIncludes `BeeL-Signature`, `BeeL-Event`, `BeeL-Event-Id`, `BeeL-Delivery-Id`, and `Idempotency-Key`.\n","additionalProperties":{"type":"string"},"example":{"BeeL-Signature":"t=1741362026,v1=3c4f7a2e1b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f","BeeL-Event":"verifactu.status.updated","BeeL-Event-Id":"8ee6b023-c4e5-482e-93ca-dc66da2f9cb5","BeeL-Delivery-Id":"b3873959-67d8-4a11-87ba-b39cc2d395fe","Idempotency-Key":"8ee6b023-c4e5-482e-93ca-dc66da2f9cb5"}},"delivered_at":{"type":"string","format":"date-time"}}},"RequestLogSummary":{"type":"object","description":"Summary of an API request (list view).","required":["request_id","timestamp","http_method","http_path","http_status"],"properties":{"request_id":{"type":"string","description":"Request correlation identifier (X-Request-Id header).","example":"6f9619ff-8b86-d011-b42d-00cf4fc964ff"},"timestamp":{"type":"string","format":"date-time","description":"Time the request was received."},"http_method":{"type":"string","example":"POST"},"http_path":{"type":"string","example":"/v1/invoices"},"http_status":{"type":"integer","example":422},"duration_ms":{"type":"integer","format":"int64","nullable":true,"description":"Request latency in milliseconds."},"environment":{"allOf":[{"$ref":"#/components/schemas/Environment"}],"nullable":true},"api_key_id":{"type":"string","format":"uuid","nullable":true,"description":"Id of the API key the request authenticated with."},"error_code":{"type":"string","nullable":true,"description":"Public error code returned (e.g. INVOICE_NOT_FOUND). Null if the request succeeded.","example":"VALIDATION_ERROR"},"client_ip":{"type":"string","nullable":true}}},"RequestLogCursorPagination":{"type":"object","description":"Cursor-based pagination (keyset on _time + request_id). APL does not support cheap offset.","required":["has_next","has_previous"],"properties":{"next_cursor":{"type":"string","nullable":true,"description":"Opaque cursor for the next (older) page. Null if there are no more."},"prev_cursor":{"type":"string","nullable":true,"description":"Opaque cursor for the previous (newer) page. Null if this is the first."},"has_next":{"type":"boolean"},"has_previous":{"type":"boolean"}}},"RequestLogListResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"type":"object","required":["request_logs"],"properties":{"request_logs":{"type":"array","items":{"$ref":"#/components/schemas/RequestLogSummary"}},"pagination":{"$ref":"#/components/schemas/RequestLogCursorPagination"}}},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"RequestLogDetail":{"description":"Full detail of a request, including bodies and headers.","allOf":[{"$ref":"#/components/schemas/RequestLogSummary"},{"type":"object","properties":{"query_string":{"type":"string","nullable":true},"user_agent":{"type":"string","nullable":true},"request_body":{"type":"string","nullable":true,"description":"Request body (truncated by the ingestor; no sensitive headers)."},"response_body":{"type":"string","nullable":true,"description":"Response body (truncated by the ingestor)."},"request_headers":{"type":"object","nullable":true,"additionalProperties":{"type":"string"}},"response_headers":{"type":"object","nullable":true,"additionalProperties":{"type":"string"}}}}]},"RequestLogSingleResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","example":true},"data":{"$ref":"#/components/schemas/RequestLogDetail"},"meta":{"$ref":"#/components/schemas/ResponseMeta"}}},"EmailDeliverySortBy":{"type":"string","description":"Email delivery field the list is ordered by: `sent_at` the instant of the attempt,\n`status` the delivery status, `email_type` the kind of email.\n","enum":["sent_at","status","email_type"],"example":"sent_at"}},"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"beel_sk_*","description":"Keys are prefixed `beel_sk_`, and each one carries the scopes it was created with: a key\nshort of the scope an operation needs is answered `403`. The scope an operation requires\nis shown next to its title, and the full catalogue lives in the Scopes reference.\n\nKeys are created from the BeeL dashboard. They are secret credentials: do not share them\nor commit them to source control.\n"},"SessionCookie":{"type":"apiKey","in":"cookie","name":"BEEL_SESSION","description":"Session Cookie authentication (for browser-based access).\n\n**Note:** This method is primarily used by the web application.\nFor programmatic access, use API Keys instead.\n"},"OAuth2":{"type":"oauth2","description":"Access tokens issued by the BeeL. authorization server (issuer `https://app.beel.es/api`, metadata at https://app.beel.es/api/.well-known/oauth-authorization-server) are accepted exactly like an API key: `Authorization: Bearer <access_token>`.\n\nAuthorization code with PKCE (S256) and refresh tokens. Request only the scopes the operation declares — a token short of one is answered `403 INSUFFICIENT_SCOPE` with `error.details.missing_scopes`.\n\nScope catalogue and semantics: https://docs.beel.es/auth/scopes.","flows":{"authorizationCode":{"authorizationUrl":"https://app.beel.es/api/oauth2/authorize","tokenUrl":"https://app.beel.es/api/oauth2/token","refreshUrl":"https://app.beel.es/api/oauth2/token","scopes":{"accounts:read":"Read provisioned (managed) accounts","accounts:write":"Create, update and delete provisioned (managed) accounts","companies:list":"List companies (NIFs) and their stats","companies:read":"Read companies (NIFs)","companies:write":"Create, update and delete companies (NIFs)","configuration:read":"Read account and fiscal configuration","configuration:write":"Create, update and delete account and fiscal configuration","customers:read":"Read customers","customers:write":"Create, update and delete customers","emails:read":"Read email deliveries","invoices:read":"Read invoices","invoices:write":"Create, update and delete invoices","logs:read":"Read API request logs","members:read":"Read members and invitations","members:write":"Create, update and delete members and invitations","nif:validate":"Validate Spanish tax IDs (NIF/CIF) against the AEAT census","payment-connections:read":"Read payment connections (Stripe)","payment-connections:write":"Create, update and delete payment connections (Stripe)","products:read":"Read products","products:write":"Create, update and delete products","sandbox":"Operate in the sandbox environment (test keys, AEAT test mode, no quota consumed)","series:read":"Read invoice numbering series","series:write":"Create, update and delete invoice numbering series","webhooks:read":"Read webhook subscriptions","webhooks:write":"Create, update and delete webhook subscriptions"}}}}},"responses":{"UnexpectedError":{"description":"Any status code the operation does not list above. Every operation declares it, so a\ngenerated client always has a branch to fall into and never loses the cause of a failure\nit did not anticipate.\n\nThis is where the transport-level answers land — `405`, `406`, `415` and `429` — together\nwith any status a future version of the API starts returning. All of them carry the same\nerror envelope as the codes listed explicitly, so `error.code` is what tells them apart:\nswitching on the status code alone is not enough. See «Transport-level errors» in the\nAPI description for when each one is produced.\n\nA `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active\nsubscription. `error.details` says why and what to do: `current_status` (absent if it\nnever had one), `required_permission` and `action`.\n\nA `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a\ndependency of BeeL itself (the database, the email provider) was momentarily\nunavailable and nothing was done. Retry after those seconds.\n\nA `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the\noperation depends on failed or did not answer in time. It is a transient condition — retry\nwith the same `Idempotency-Key` where the operation accepts one.\n\nOne exception to the envelope: a failure of the network edge that never reaches the\napplication (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not\nBeeL's — it may not even be JSON. Treat those as \"no answer\", and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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"}}}}},"Unauthorized":{"description":"Missing or invalid authentication. Like every other error, `message`/`detail` is\nlocalized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the\nheader is missing or asks for none of those.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNAUTHORIZED","message":"Authentication is required to access this resource"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"TooManyRequests":{"description":"Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is\nover its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because\ntoo many went out to that recipient in a short time. Either way, retry after the\n`Retry-After` seconds.\n\n`QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan\nfor the current period (issued invoices, for instance). Retrying does not help until the\nquota resets or the plan changes. `error.details` carries `feature_code`,\n`current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until\nthe reset when it is known.\n","headers":{"Retry-After":{"description":"Seconds until the rate limit resets","schema":{"type":"integer"}},"RateLimit-Limit":{"description":"Maximum requests allowed in the window","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Remaining requests in the current window","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the current window resets","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"RATE_LIMIT_EXCEEDED","message":"Too many requests. Please try again in 60 seconds."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INTERNAL_ERROR","message":"Internal server error"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"BadRequest":{"description":"Bad request. From a dashboard session, a `BeeL-Environment` header whose value is not a\nknown environment answers `ENVIRONMENT_HEADER_INVALID`, with the header named in\n`error.details` (`{\"BeeL-Environment\": \"invalid\"}`); it is never read as production.\n\n`BUSINESS_RULE_VIOLATION` is the generic answer to an operation the resource does not\nadmit in its current state when that state has no code of its own. It carries no\n`details`; read the resource again before retrying.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"BAD_REQUEST","message":"Invalid request"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"Forbidden":{"description":"Authenticated but not allowed. Fourteen causes shared by every operation, told apart by\n`error.code`; an operation that adds a cause of its own names it in its `403`. You can\nbranch on them exhaustively.\n\n- `INSUFFICIENT_SCOPE` — the credential lacks a scope the operation requires;\n  `error.details.missing_scopes` names them as a single comma-separated string (for\n  example `\"invoices:write,customers:read\"`), not as an array; `required_scopes` has the\n  same shape and lists every scope the operation needs. Retrying will not help: mint a key\n  that holds them.\n- `COMPANY_READ_ONLY` — the scope is there, but your access level over that NIF only\n  lets you read it.\n- `ACCOUNT_MANAGEMENT_FORBIDDEN` — the scope is there, but your role over the account,\n  or the management relationship you hold over it, does not cover this operation.\n- `ACCOUNT_NOT_ACCESSIBLE` — the account is not yours to reach, which is also the answer\n  when it does not exist, so existence is never disclosed.\n- `ACTIVE_COMPANY_NOT_ACCESSIBLE` — the same, for a NIF: the company in the path, or the\n  one named by `BeeL-Active-Company`, is not one this credential may reach — and again,\n  this is also the answer when it does not exist.\n- `COMPANY_ACCESS_REVOKED` — your access to the NIF was withdrawn while the request was\n  already in flight, so the write was rejected and nothing was recorded. Retrying will\n  not help until the access is granted again.\n- `LIVE_CREDENTIAL_REQUIRED` — the operation changes the real account and the call came\n  from a test API key (`beel_sk_test_…`). Use your live key or the dashboard.\n- `FEATURE_NOT_AVAILABLE` — your subscription does not include the entitlement the\n  operation needs; `error.details.feature_code` names it. This gate runs **before** the\n  scope gate, so for such an operation you never see `INSUFFICIENT_SCOPE` first.\n- `NO_ACTIVE_ACCOUNT` — the credential does not resolve to an account, so no scope can be\n  evaluated against one. Fail-closed, not a permission that can be granted to you.\n- `OPERATION_REQUIRES_SESSION` — the operation is available only from the web session; no\n  API key and no OAuth2 token can perform it, whatever scopes it holds.\n- `OPERATION_REQUIRES_API_KEY` — the reverse: the operation needs a scope that only an API\n  key can hold (such as `accounts:write`), so a dashboard session never satisfies it.\n- `API_KEY_LIVE_NOT_AVAILABLE` — the call came with a live API key (`beel_sk_live_…`) and\n  the account has no Live mode yet. It is never degraded to Test: use your test key, or\n  activate Live first.\n- `ACCOUNT_REQUIRES_FOCUS_SWITCH` — dashboard session only: you are a member of the account\n  in the path, but it is not the account in focus. Switch focus to it and retry.\n- `REQUEST_BLOCKED` — the edge security filter stopped the request before the API saw its\n  body, usually because a text field carries something that looks like markup or a\n  script (HTML tags, `<script>`). Review the body and retry.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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"}}}}},"ValidationError":{"description":"`VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from\nthe offending property's name (in the contract's `snake_case`, nested paths joined with a dot,\ne.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry\nper property. When the rejected value is one outside an enum's vocabulary, `details` instead\nfollows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`).\n\nTwo more codes share this status for a value that passes the contract but does not fit\nwhere it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and\n`VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"legal_name":"The field 'legal_name' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"InvalidRequestTarget":{"description":"The request target could not be read as declared, so nothing was looked up. Three causes,\nall named in `error.details`: a path or query parameter whose value does not parse as its\ntype (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty\nelement in a list such as `status=ISSUED,`), a required parameter that was not sent\n(`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not\n(`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is\nnot an error: it is the same as omitting it.\n\nThe fault is in the target, not in the content — which is what `422` is defined over.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The parameter 'invoice_id' has an invalid type. Expected: UUID.","details":{"field":"invoice_id","invalid_value":"deliveries","expected_format":"UUID"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"NotFound":{"description":"The resource addressed by the path does not exist, or is not one this credential can see —\nthe two are answered identically, so existence is never disclosed. `error.code` names the\nkind of resource that was missing, so a client can tell which of several ids in a path\nfailed to resolve:\n\n- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).\n- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.\n- `CLIENT_NOT_FOUND` — the `{customer_id}`.\n- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not\n  resolve to a customer of the issuing company.\n- `PRODUCT_NOT_FOUND` — the `{product_id}`.\n- `SERIES_NOT_FOUND` — the `{series_id}`.\n- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the\n  `{delivery_id}`.\n- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder\n  has not completed onboarding, so there is no issuer to act as.\n- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.\n\nSome resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,\n`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it\nis documented on the operation.\n\nA `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist\nin this API (a typo in the route, or a resource that was never here). It says nothing about\nany resource. When the path exists but not with that method, the answer is `405`, not `404`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVOICE_NOT_FOUND","message":"Invoice not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"StateConflict":{"description":"The request is well formed but collides with the state that is already there. Three\nfamilies, told apart by `error.code`:\n\n- **Uniqueness** — something with that identity already exists (`CLIENT_DUPLICATE`,\n  `PRODUCT_DUPLICATE`, `SERIES_CODE_DUPLICATED`, `NIF_ALREADY_REGISTERED`,\n  `MEMBER_ALREADY_IN_ACCOUNT`, `INVOICE_DUPLICATE_EXTERNAL_REFERENCE`).\n- **Lifecycle** — the transition does not apply from where the resource is\n  (`INVOICE_ALREADY_VOIDED`, `PROFORMA_ALREADY_CONVERTED`, `CLIENT_HAS_INVOICES`,\n  `RECURRING_ALREADY_ENDED`, `RECURRING_OCCURRENCE_ALREADY_CONSUMED` — the occurrence the\n  recurring template points at has already been billed; read its history instead of\n  retrying).\n  Read the resource before retrying: the state you assumed is not its state.\n- **Concurrency and idempotency** — another write got there first\n  (`CONCURRENT_MODIFICATION`), or the `Idempotency-Key` you sent is still in flight\n  (`IDEMPOTENCY_KEY_PROCESSING`, retry after a moment) or was already used for a\n  different body (`IDEMPOTENCY_KEY_MISMATCH`, use a new key).\n\n`CONFLICT` is the generic fallback of the uniqueness family, when the uniqueness that was\nviolated has no code of its own.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"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"}}}}},"UnprocessableEntity":{"description":"Unprocessable entity - validation or business rule error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"Data cannot be processed","details":{"field":"Specific error description"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"UnprocessableQueryParameter":{"description":"A parameter parsed as its declared type but its value is not one the operation admits:\na `limit` outside its range (`limit=0`, `limit=101`), a `page` under 1, a `cursor` that is\nnot one this API issued, more keys than the `metadata` filter accepts, a value outside the\nsupported set. The query was rejected whole and nothing was read.\n\nWhere a `400` says the target could not be understood, this says it was understood and\nrefused. Fix the value — retrying the same query answers the same way.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Validation constraint violation.","details":{"limit":"must be greater than or equal to 1"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"InvalidJsonFormat":{"description":"`INVALID_JSON_FORMAT` — the body is not valid JSON, or a property has the wrong type or format\n(a string where a number is expected, a date that does not parse, a malformed UUID, a boolean\nthat is not `true`/`false`). The `details` object follows `FieldDeserializationError`:\n`field`, `invalid_value` and, where there is one, `expected_format` (or `allowed_values`, for a\nboolean). A property the operation does not declare is not a format error: it is ignored.\n\nA value outside an **enum**'s vocabulary is not answered here. The property is a well-formed\nstring that names nothing the operation knows, so it is judged as content: `422`\n(`VALIDATION_ERROR`), with the same `FieldDeserializationError` shape in `details`\n(`field`, `invalid_value`, `allowed_values`).\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/FieldDeserializationError"}}}]}}}]},"example":{"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"}}}}},"CustomerImportRejected":{"description":"The file was rejected as a whole, before any record was looked at: it is not valid CSV, its\nencoding is not UTF-8, required columns are missing, it is over the size limit, or it carries\nmore records than the operation accepts.\n\nWhich one it was travels in `error.code`, and the values behind it in `error.details`:\n`missing_headers` and `found_headers` for `MISSING_HEADERS`, `record_count` and\n`max_records` for `TOO_MANY_RECORDS`, `file_size_mb` and `max_file_size_mb` for\n`CSV_FILE_TOO_LARGE`. A rejection with nothing to quantify carries no `details`.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/CsvImportErrorDetails"}}}]}}}]},"example":{"success":false,"error":{"code":"MISSING_HEADERS","message":"Required headers not found: 'telefono', 'direccion_calle'. Headers found: nombre_fiscal, nif, email","details":{"missing_headers":["telefono","direccion_calle"],"found_headers":["nombre_fiscal","nif","email"]}},"meta":{"timestamp":"2026-03-05T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"ForbiddenMemberManagement":{"description":"Every cause listed under the plain `403` above, plus the one that governs the people of an account: `MEMBER_MANAGEMENT_FORBIDDEN`, returned when you reach the account but your role in it does not manage its members, invitations or grants. Only an `OWNER` or an `ADMIN` does; a `MEMBER` gets this code, and so does an API key minted by one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"MEMBER_MANAGEMENT_FORBIDDEN","message":"Only the owner or an admin can manage account members."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"MemberNotFound":{"description":"`MEMBER_NOT_FOUND` — no member with that id belongs to the account. Membership ids are not shared between accounts, so a member of another account answers the same way.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"MEMBER_NOT_FOUND","message":"No member with that id belongs to this account."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"ForbiddenOwnerReserved":{"description":"Every cause listed under the plain `403` above, plus the three that govern a change to a member: `MEMBER_MANAGEMENT_FORBIDDEN`, when your role in the account does not manage its members (only an `OWNER` or an `ADMIN` does); `OWNER_ROLE_RESERVED`, when the member you act on is an `OWNER` and you are not one, since an `ADMIN` manages members but not owners; and `LIVE_CREDENTIAL_REQUIRED`, when the call is made with a test API key (`beel_sk_test_…`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"OWNER_ROLE_RESERVED","message":"Only an owner can assign or transfer the owner role."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"ForbiddenMemberManagementLiveCredentialRequired":{"description":"Every cause listed under the plain `403` above, plus the two that govern a change to the people of an account: `MEMBER_MANAGEMENT_FORBIDDEN`, when your role in the account does not manage its members, invitations or grants (only an `OWNER` or an `ADMIN` does), and `LIVE_CREDENTIAL_REQUIRED`, when the call is made with a test API key (`beel_sk_test_…`): members, invitations and grants are shared between Test and Live, so changing them is always a real change. Use your live API key or the dashboard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"LIVE_CREDENTIAL_REQUIRED","message":"This operation changes the real account; it requires a live API key or a dashboard session."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"MemberGrantNotFound":{"description":"Two causes, told apart by `error.code`: `MEMBER_NOT_FOUND` (no member with that id belongs to the account) and `GRANT_NOT_FOUND` (the member exists but holds no grant over that company).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"GRANT_NOT_FOUND","message":"The member holds no grant over that company."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"ForbiddenSessionOnly":{"description":"Every cause listed under the plain `403` above, plus the one that governs this operation: `OPERATION_REQUIRES_SESSION` — \"This operation is only available from the web session; it cannot be performed with an API key or OAuth.\" It is returned to **every** machine credential, live key included, whatever scopes it holds, and before any other check, so it is not a permission you can be granted and retrying with a different key never helps. The operation is reserved to a signed-in human in the dashboard because a leaked key must not be able to take an account over. From the session, two more: `OWNER_ROLE_RESERVED`, when the session belongs to an `ADMIN`, since only an `OWNER` hands ownership over, and `MEMBER_MANAGEMENT_FORBIDDEN`, when the session's role does not manage members.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"OPERATION_REQUIRES_SESSION","message":"This operation is only available from the web session; it cannot be performed with an API key or OAuth."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"InvitationNotFound":{"description":"`INVITATION_NOT_FOUND` — no member invitation with that id exists in the account, in any state.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVITATION_NOT_FOUND","message":"No invitation with that id in this account."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"ForbiddenLiveCredentialRequired":{"description":"Every cause listed under the plain `403` above, plus one specific to writes on the account's control plane: `LIVE_CREDENTIAL_REQUIRED`, returned when the call is made with a test API key (`beel_sk_test_…`). Members, invitations and grants are shared between Test and Live, so changing them is always a real change to the real account — there is no sandbox rehearsal of it. The same applies to changing a managed account's access level and to ending its management: both act on an account that may already be invoicing. Use your live API key or the dashboard; reading these resources keeps working with either key, and so does the whole account provisioning flow (provisioning and claim tokens), which creates an inert account and is what you are meant to rehearse in Test — with your test key, but still only if your subscription carries the `manage_accounts` entitlement: that gate applies in Test exactly as it does in Live, and without it the rehearsal answers `403 FEATURE_NOT_AVAILABLE` rather than this code. Accounts that have no live mode manage their people from the dashboard.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"LIVE_CREDENTIAL_REQUIRED","message":"This operation changes the real account; it requires a live API key or a dashboard session."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"PayloadTooLarge":{"description":"Payload too large. On this operation that normally means an uploaded file\nexceeded its own limit (`FILE_TOO_LARGE`). Any request may also be\nrejected with `REQUEST_BODY_TOO_LARGE` when the body as a whole exceeds\nthe maximum described under *Request body size*. In both cases the\n`details` object carries the applicable maximum, except when the HTTP\nconnector cuts the request first, which carries no `details`.\n","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"error":{"allOf":[{"$ref":"#/components/schemas/ErrorDetail"},{"type":"object","properties":{"details":{"$ref":"#/components/schemas/PayloadTooLargeDetails"}}}]}}}]},"example":{"success":false,"error":{"code":"FILE_TOO_LARGE","message":"The file exceeds the maximum allowed size of 5.0 MB.","details":{"max_size_bytes":"5242880","max_size_formatted":"5.0 MB"}},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"SeriesValidationError":{"description":"The series is rejected. `error.code` is `VALIDATION_ERROR` when the body parses but a value\nis not acceptable: `details` is a flat map from the offending property's name (in the\ncontract's `snake_case`) to a message describing what is wrong with it, or follows\n`FieldDeserializationError` when the value is outside an enum's vocabulary. Otherwise it is\none of the series rules, among them:\n- `SERIES_MONTHLY_REQUIRES_MONTH_AND_YEAR` / `SERIES_ANNUAL_REQUIRES_YEAR` — the format\n  cannot tell the counter reset periods apart.\n- `SERIES_INITIAL_NUMBER_OUT_OF_RANGE` — the initial number is out of range.\n- `SERIES_FORMAT_NUMBER_TOO_LONG` — the longest number the series can generate (the\n  format with the code, the year, the month and the counter substituted) exceeds the 60\n  characters the AEAT accepts for the invoice number. The counter counts as at least 9\n  digits, with or without padding: `{NUM:X}` is a minimum width and the number keeps\n  growing past it.\n- `SERIES_FORMAT_INVALID_CHARACTERS` — that number would contain a character the AEAT does\n  not accept: only printable ASCII (32-126) is allowed, except `\"`, `'`, `<`, `>` and `=`.\n- `SERIES_TYPE_CHANGE_LEAVES_TYPE_UNCOVERED` — changing `document_type` of the default\n  series would leave its current document type, which still has other active series,\n  without a default. Make one of those the default first.\n- `SERIES_COMPANY_DELETED` — the company was deleted while the request was running: its\n  series keep their numbering but can no longer be activated or made the default.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"SERIES_FORMAT_NUMBER_TOO_LONG","message":"The number this series generates can reach 64 characters and the AEAT accepts at most 60 (series and number together). Shorten the code or the fixed text of the format"},"meta":{"timestamp":"2026-09-24T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"PaymentConnectionNotFound":{"description":"`CONNECTION_NOT_FOUND` — no connection of this NIF carries this `{connection_id}`. It speaks of the connection, never of the NIF: a NIF you cannot reach answers `403`, while a connection of another NIF answers this same `404` so that naming it reveals nothing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"CONNECTION_NOT_FOUND","message":"Payment connection not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"CompanyNotAccessible":{"description":"Your account does not own or manage this company. The same answer is given for a\nNIF that does not exist.\nThat case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"EventNotOfThisConnection":{"description":"`CONNECTION_NOT_FOUND` when no connection of this NIF carries this `{connection_id}`;\n`EVENT_NOT_FOUND` when the connection is yours but the event is not its own. Both answer\nalike to an event that does not exist: the `404` speaks of the connection and the event,\nnever of the NIF. A NIF you cannot reach answers `403`; a connection of another NIF\nanswers this `404`, so naming it reveals nothing.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"EVENT_NOT_FOUND","message":"Payment event not found"},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"WebhookSubscriptionValidationError":{"description":"The body parses but a value is not acceptable. Branch on `error.code`:\n\n- `VALIDATION_ERROR` — a field fails validation; `error.details` maps each offending\n  property to what is wrong with it.\n- `URL_MUST_BE_HTTPS` — `url` does not use `https`.\n- `WEBHOOK_URL_INVALID` — `url` is not a valid absolute URL.\n- `URL_TARGET_NOT_ALLOWED` — `url` points to an internal network address: a private,\n  loopback or link-local range, either as a literal IP or through a host name that\n  resolves to one. Use an endpoint reachable from the internet. A host name that does not\n  resolve yet is accepted, and deliveries to an internal address are refused anyway.\n- `WEBHOOK_REACTIVATION_REQUIRES_LIVE_ENDPOINT` — on update only: reactivating a\n  subscription that BeeL paused sends a test delivery first, and it failed. Fix the\n  endpoint and retry; nothing was changed.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"URL_TARGET_NOT_ALLOWED","message":"The webhook URL points to an internal network address. Use an endpoint reachable from the internet."},"meta":{"timestamp":"2026-03-07T16:22:10Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"UnprocessableRequestLogQuery":{"description":"The query was understood and refused. `error.code` is `REQUEST_LOG_INVALID_CURSOR` when `cursor` is not a cursor this API issued, `REQUEST_LOG_INVALID_TIME_RANGE` when `from` is later than `to` (with either one defaulted: `to` is now and `from` a window before `to`), or `VALIDATION_ERROR` when another query parameter falls outside its declared range. Send the `cursor` exactly as returned by the previous page; a hand-built or truncated value is never accepted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"REQUEST_LOG_INVALID_CURSOR","message":"The pagination cursor is not valid."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}},"RequestLogNotFound":{"description":"`REQUEST_LOG_NOT_FOUND` — no request log with that correlation identifier is readable: it does not exist, was made by another user, or belongs to the other environment. Which of the three is never disclosed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"REQUEST_LOG_NOT_FOUND","message":"No request log with that identifier is available."},"meta":{"timestamp":"2025-01-15T10:30:00Z","request_id":"4bf92f3577b34da6a3ce929d0e0e4736"}}}}}},"examples":{"list-invoices-success":{"summary":"List invoices (200)","description":"Invoice list with pagination metadata","value":{"success":true,"data":{"invoices":[{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","invoice_number":"A/2025/0042","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":42,"type":"STANDARD","status":"ISSUED","issue_date":"2025-01-20","due_date":"2025-02-20","issuer":{"legal_name":"Mi Empresa SL","nif":"B12345674","address":{"street":"Calle Principal","number":"10","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321","address":{"street":"Calle Secundaria","number":"20","postal_code":"08001","city":"Barcelona","province":"Barcelona","country":"España"}},"lines":[{"description":"Consulting services","quantity":10,"unit":"hours","unit_price":150,"taxable_base":1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":1815}],"totals":{"taxable_base":1500,"total_vat":315,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":1500,"amount":315}],"invoice_total":1815},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf","created_at":"2025-01-20T09:00:00Z","updated_at":"2025-01-20T09:30:00Z"},{"id":"550e8400-e29b-41d4-a716-446655440002","invoice_number":"A/2025/0041","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":41,"type":"STANDARD","status":"PAID","issue_date":"2025-01-15","due_date":"2025-02-15","payment_date":"2025-01-18","sent_at":"2025-01-16T10:00:00Z","paid_at":"2025-01-18T14:00:00Z","issuer":{"legal_name":"Mi Empresa SL","nif":"B12345674","address":{"street":"Calle Principal","number":"10","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"456e7890-e12b-34d5-a678-901234567890","legal_name":"Otro Cliente SA","nif":"A12345678","address":{"street":"Avenida Central","number":"100","postal_code":"46001","city":"Valencia","province":"Valencia","country":"España"}},"lines":[{"description":"Software development","quantity":50,"unit":"hours","unit_price":50,"taxable_base":2500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":3025}],"totals":{"taxable_base":2500,"total_vat":525,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":2500,"amount":525}],"invoice_total":3025},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/550e8400-e29b-41d4-a716-446655440002/pdf","created_at":"2025-01-15T10:00:00Z","updated_at":"2025-01-18T14:00:00Z"},{"id":"550e8400-e29b-41d4-a716-446655440003","invoice_number":"A/2025/0040","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":40,"type":"STANDARD","status":"OVERDUE","issue_date":"2024-12-10","due_date":"2025-01-10","issuer":{"legal_name":"Mi Empresa SL","nif":"B12345674","address":{"street":"Calle Principal","number":"10","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"789e0123-e45b-67d8-a901-234567890123","legal_name":"Cliente Moroso SL","nif":"B98765432","address":{"street":"Plaza Mayor","number":"5","postal_code":"41001","city":"Sevilla","province":"Sevilla","country":"España"}},"lines":[{"description":"Web maintenance","quantity":1,"unit":"unit","unit_price":800,"taxable_base":800,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":968}],"totals":{"taxable_base":800,"total_vat":168,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":800,"amount":168}],"invoice_total":968},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/550e8400-e29b-41d4-a716-446655440003/pdf","created_at":"2024-12-10T11:00:00Z","updated_at":"2024-12-10T11:00:00Z"}],"pagination":{"current_page":1,"items_per_page":20,"total_items":3,"total_pages":1,"has_next":false,"has_previous":false}},"meta":{"timestamp":"2025-01-20T12:00:00Z","request_id":"550e8400-e29b-41d4-a716-446655440000"}}},"create-basic":{"summary":"Basic ordinary invoice","description":"Registered customer, 21% VAT, single service line","value":{"type":"STANDARD","recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000"},"lines":[{"description":"Corporate website development","quantity":40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"notes":"Payment via bank transfer"}},"create-with-adhoc-customer":{"summary":"Invoice with ad-hoc customer","description":"Unregistered customer, inline data, multiple line items","value":{"type":"STANDARD","recipient":{"legal_name":"Comercial Martínez SL","nif":"B87654321","email":"contacto@comercialmartinez.es","phone":"+34912345678","address":{"street":"Avenida de la Constitución","number":"45","floor":"3º B","postal_code":"41001","city":"Sevilla","province":"Sevilla","country":"España"}},"lines":[{"description":"Business strategic consulting","quantity":8,"unit":"hours","unit_price":125,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}},{"description":"Executive report preparation","quantity":1,"unit":"document","unit_price":500,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"notes":"Invoice for consulting services for the month of January.\nIncludes 8 hours of consulting and report preparation.\n"}},"create-issue-directly":{"summary":"Create, issue directly, and send by email","description":"Skip draft state. Creates invoice directly as ISSUED with:\n- Definitive invoice number assigned\n- PDF generated (sync or async based on options.wait_for_pdf)\n- Email sent automatically if options.send_automatically=true\n","value":{"type":"STANDARD","recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000"},"lines":[{"description":"Monthly web application maintenance","quantity":1,"unit":"month","unit_price":850,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}],"payment_info":{"method":"DIRECT_DEBIT","iban":"ES9121000418450200051332"},"options":{"issue_directly":true,"wait_for_pdf":true,"send_automatically":true}}},"create-with-custom-email":{"summary":"Create invoice with custom email recipients","description":"Issues invoice and sends to multiple recipients with:\n- Custom recipient list (overrides recipient.email)\n- CC to accounting department\n- Personalized subject and message\n","value":{"type":"STANDARD","due_date":"2025-02-26","recipient":{"legal_name":"Acme Corporation SL","nif":"B12345674","email":"info@acme.es","address":{"street":"Calle Gran Vía","number":"42","floor":"5º","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"}},"lines":[{"description":"Mobile application development - Phase 1","quantity":80,"unit":"hours","unit_price":65,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15},{"description":"Application UX/UI design","quantity":20,"unit":"hours","unit_price":55,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}],"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"notes":"Project APP-2025. Includes source code and technical documentation.","metadata":{"project_code":"APP-2025-001","purchase_order":"PO-ACME-2025-042"},"options":{"issue_directly":true,"wait_for_pdf":true,"send_automatically":true,"email_config":{"recipients":["facturas@acme.es","pedro.garcia@acme.es","maria.lopez@acme.es"],"cc":["contabilidad@miempresa.com","gestor@asesoria.es"],"subject":"Invoice project APP-2025 - Mobile application development","message":"Dear Acme Corporation team,\n\nPlease find attached the invoice corresponding to the first phase\nof the mobile application development as agreed.\n\nPayment is due within 30 days. For any questions,\nplease do not hesitate to contact us.\n\nKind regards.\n"}}}},"under-3000":{"summary":"Simplified invoice without NIF under 3000 EUR","description":"Ticket invoice for consumer without NIF, total under the 3000 EUR absolute cap","value":{"type":"SIMPLIFIED","recipient":{},"lines":[{"description":"Menú del día","quantity":2,"unit":"unit","unit_price":14.5,"main_tax":{"type":"IVA","percentage":10,"regime_key":"01"}}],"options":{"issue_directly":true}}},"spanish-b2b":{"summary":"Standard B2B invoice - Spanish business customer","description":"Invoice to ACCIONA SA with full address and 30-day payment term","value":{"type":"STANDARD","due_date":"2026-06-19","recipient":{"legal_name":"ACCIONA SA","nif":"A08001851","address":{"street":"Avenida de Europa","number":"18","postal_code":"28108","city":"Alcobendas","province":"Madrid","country":"España","country_code":"ES"}},"lines":[{"description":"Consultoría de arquitectura — Sprint mayo","quantity":40,"unit":"hours","unit_price":75,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"options":{"issue_directly":true}}},"multiple-vat-rates":{"summary":"Standard invoice with multiple VAT rates","description":"Three lines with 21%, 10%, and 4% VAT rates mixed","value":{"type":"STANDARD","recipient":{"legal_name":"ACCIONA SA","nif":"A08001851","address":{"street":"Calle Cava Baja","number":"35","postal_code":"28005","city":"Madrid","province":"Madrid","country":"España","country_code":"ES"}},"lines":[{"description":"Vino reserva (caja 6 botellas)","quantity":4,"unit":"box","unit_price":90,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}},{"description":"Servicio de catering — menú degustación","quantity":25,"unit":"menu","unit_price":32,"main_tax":{"type":"IVA","percentage":10,"regime_key":"01"}},{"description":"Pan artesano","quantity":50,"unit":"unit","unit_price":1.2,"main_tax":{"type":"IVA","percentage":4,"regime_key":"01"}}],"options":{"issue_directly":true}}},"derived_unit_price_out_of_range_request":{"summary":"Declared total over a minimal quantity (rejected, 422)","description":"A line priced by declared total does not carry `unit_price`: it is derived as\ntotal ÷ quantity. Neither operand is out of range on its own — `total_excluding_tax` is at its\ndeclared maximum and `quantity` declares no minimum at all — so no `maximum` in the contract\ncan bound the quotient, and it is the quotient that does not fit. The rejection points at the\nline and publishes both numbers.\n","value":{"type":"STANDARD","recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000"},"lines":[{"description":"Consulting","quantity":0.01,"total_excluding_tax":99999999.99,"main_tax":{"type":"IVA","percentage":21}}]}},"irpf-withholding":{"summary":"Standard invoice with IRPF withholding (professional services)","description":"Professional services with 15% IRPF tax retention","value":{"type":"STANDARD","recipient":{"legal_name":"ACCIONA SA","nif":"A08001851","address":{"street":"Avenida de Europa","number":"18","postal_code":"28108","city":"Alcobendas","province":"Madrid","country":"España","country_code":"ES"}},"lines":[{"description":"Servicios de abogacía — asesoramiento mercantil","quantity":12,"unit":"hours","unit_price":120,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15}],"options":{"issue_directly":true}}},"recargo-equivalencia":{"summary":"Standard invoice with equivalence surcharge (RECAREGO)","description":"Retail invoice with 21% IVA + 5.2% equivalence surcharge (regime_key 18)","value":{"type":"STANDARD","recipient":{"legal_name":"ACCIONA SA","nif":"A08001851","address":{"street":"Calle del Comercio","number":"56","postal_code":"08001","city":"Barcelona","province":"Barcelona","country":"España","country_code":"ES"}},"lines":[{"description":"Lote de auriculares inalámbricos para reventa","quantity":30,"unit":"unit","unit_price":45,"main_tax":{"type":"IVA","percentage":21,"regime_key":"18"},"equivalence_surcharge_rate":5.2}],"options":{"issue_directly":true}}},"b2b-intra-eu-goods-e5":{"summary":"B2B intra-EU goods (E5) - exempt Art. 25","description":"EU-to-EU goods sale to German business with NIF_IVA, 0% IVA exempt","value":{"type":"STANDARD","recipient":{"legal_name":"Berliner Industrie GmbH","alternative_id":{"type":"NIF_IVA","number":"DE123456789","country_code":"DE"},"address":{"street":"Friedrichstraße","number":"200","postal_code":"10117","city":"Berlin","province":"Berlin","country":"Alemania","country_code":"DE"}},"lines":[{"description":"Componentes electrónicos — pedido EU-2026-0042","quantity":500,"unit":"unit","unit_price":8.4,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"EXENTA_ART_25"}],"options":{"issue_directly":true}}},"b2b-intra-eu-services-n2":{"summary":"B2B intra-EU services (N2) - no sujeta localization","description":"EU-to-EU services to German business, not subject to Spanish VAT","value":{"type":"STANDARD","recipient":{"legal_name":"Berliner Industrie GmbH","alternative_id":{"type":"NIF_IVA","number":"DE123456789","country_code":"DE"},"address":{"street":"Friedrichstraße","number":"200","postal_code":"10117","city":"Berlin","province":"Berlin","country":"Alemania","country_code":"DE"}},"lines":[{"description":"Consultoría de arquitectura cloud","quantity":60,"unit":"hours","unit_price":110,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"NO_SUJETA_LOCALIZACION"}],"options":{"issue_directly":true}}},"b2c-oss-over-threshold":{"summary":"B2C OSS (One-Stop-Shop) over threshold","description":"Cross-border B2C sales over 10000 EUR annual threshold, regime 17 OSS","value":{"type":"STANDARD","recipient":{"legal_name":"Anna Schmidt","alternative_id":{"type":"PASSPORT","number":"C0HJ4P9DT","country_code":"DE"},"address":{"street":"Müllerstraße","number":"47","postal_code":"80469","city":"München","province":"Bayern","country":"Alemania","country_code":"DE"}},"lines":[{"description":"Suscripción anual plataforma SaaS","quantity":1,"unit":"year","unit_price":480,"main_tax":{"type":"IVA","percentage":0,"regime_key":"17"},"exemption_reason":"NO_SUJETA_LOCALIZACION"}],"options":{"issue_directly":true}}},"b2c-under-oss-threshold":{"summary":"B2C under OSS threshold","description":"Cross-border B2C sale below 10000 EUR annual threshold, standard 21% Spanish IVA","value":{"type":"STANDARD","recipient":{"legal_name":"Anna Schmidt","alternative_id":{"type":"PASSPORT","number":"C0HJ4P9DT","country_code":"DE"},"address":{"street":"Müllerstraße","number":"47","postal_code":"80469","city":"München","province":"Bayern","country":"Alemania","country_code":"DE"}},"lines":[{"description":"Curso online — fotografía digital","quantity":1,"unit":"course","unit_price":149,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"options":{"issue_directly":true}}},"export-non-eu-goods-e2":{"summary":"Export of goods to non-EU (E2) - exempt Art. 21","description":"Non-EU goods export with 0% IVA exempt, regime_key 02","value":{"type":"STANDARD","recipient":{"legal_name":"Northeast Imports Inc.","alternative_id":{"type":"PASSPORT","number":"551234567","country_code":"US"},"address":{"street":"5th Avenue","number":"725","postal_code":"10022","city":"New York","province":"NY","country":"Estados Unidos","country_code":"US"}},"lines":[{"description":"Aceite de oliva virgen extra — palet 480 botellas","quantity":1,"unit":"pallet","unit_price":3850,"main_tax":{"type":"IVA","percentage":0,"regime_key":"02"},"exemption_reason":"EXENTA_ART_21"}],"options":{"issue_directly":true}}},"services-non-eu-n2":{"summary":"Services to non-EU (N2) - no sujeta","description":"Non-EU services not subject to Spanish VAT","value":{"type":"STANDARD","recipient":{"legal_name":"Northeast Imports Inc.","alternative_id":{"type":"PASSPORT","number":"551234567","country_code":"US"},"address":{"street":"5th Avenue","number":"725","postal_code":"10022","city":"New York","province":"NY","country":"Estados Unidos","country_code":"US"}},"lines":[{"description":"Diseño de identidad de marca","quantity":1,"unit":"project","unit_price":6500,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"NO_SUJETA_LOCALIZACION"}],"options":{"issue_directly":true}}},"exempt-art-20-medical":{"summary":"Exempt operation (E1) - educational/medical Art. 20","description":"Educational services exempt under Art. 20 LIVA","value":{"type":"STANDARD","recipient":{"legal_name":"Academia Cervantes SL","nif":"A08001851","address":{"street":"Calle Princesa","number":"27","postal_code":"28008","city":"Madrid","province":"Madrid","country":"España","country_code":"ES"}},"lines":[{"description":"Clases particulares de matemáticas — trimestre primavera","quantity":36,"unit":"hours","unit_price":28,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"EXENTA_ART_20","exemption_reason_text":"Operación exenta de IVA según Art. 20.Uno.10º LIVA (enseñanza)"}],"options":{"issue_directly":true}}},"igic-canary-islands":{"summary":"IGIC (Canary Islands) 7% instead of IVA","description":"Regional tax for Canary Islands operations","value":{"type":"STANDARD","recipient":{"legal_name":"Importaciones Atlántico SL","nif":"A08001851","address":{"street":"Calle León y Castillo","number":"200","postal_code":"35004","city":"Las Palmas de Gran Canaria","province":"Las Palmas","country":"España","country_code":"ES"}},"lines":[{"description":"Mobiliario de oficina — sillas ergonómicas","quantity":10,"unit":"unit","unit_price":180,"main_tax":{"type":"IGIC","percentage":7,"regime_key":"01"}}],"options":{"issue_directly":true}}},"not-subject-art-7-9":{"summary":"Not subject (N1) - Art. 7/9 LIVA","description":"Internal transfers or promotional samples not subject to VAT","value":{"type":"STANDARD","recipient":{"legal_name":"Distribuciones Hermanas Pérez SL","nif":"A08001851","address":{"street":"Polígono Industrial San Isidro","number":"12","postal_code":"46980","city":"Paterna","province":"Valencia","country":"España","country_code":"ES"}},"lines":[{"description":"Muestras comerciales sin valor — catálogo 2026","quantity":50,"unit":"unit","unit_price":12,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"NO_SUJETA_ART_7_9","exemption_reason_text":"Entrega de muestras gratuitas (Art. 7.2º LIVA)"}],"options":{"issue_directly":true}}},"isp-reverse-charge-s2":{"summary":"ISP reverse charge (S2) - Art. 84.2.f LIVA","description":"Construction services with buyer self-assessing VAT","value":{"type":"STANDARD","recipient":{"legal_name":"Promociones Inmobiliarias del Mediterráneo SA","nif":"A46789012","address":{"street":"Gran Vía Marqués del Turia","number":"47","postal_code":"46005","city":"Valencia","province":"Valencia","country":"España","country_code":"ES"}},"lines":[{"description":"Ejecución de obra — instalación eléctrica edificio Torre Norte","quantity":1,"unit":"project","unit_price":42000,"main_tax":{"type":"IVA","percentage":0,"regime_key":"01"},"exemption_reason":"ISP_ART_84_2_F","exemption_reason_text":"Inversión del sujeto pasivo — Art. 84.Uno.2º.f LIVA (ejecución de obra)"}],"options":{"issue_directly":true}}},"rebu-used-goods":{"summary":"REBU special regime (used goods, art, antiques)","description":"Used goods with margin-based taxation - base calculated on difference between sale and cost","value":{"type":"STANDARD","recipient":{"legal_name":"Galería de Arte Velázquez SL","nif":"A08001851","address":{"street":"Calle de Jorge Juan","number":"12","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España","country_code":"ES"}},"lines":[{"description":"Pintura óleo siglo XIX — REBU (margen): venta 3 000 € / coste 2 000 €","quantity":1,"unit":"unit","unit_price":826.45,"main_tax":{"type":"IVA","percentage":21,"regime_key":"03"}}],"notes":"Régimen especial de bienes usados (Art. 135 LIVA). Base imponible calculada sobre el margen.","options":{"issue_directly":true}}},"created-success":{"summary":"Invoice created (201)","description":"Invoice created successfully in DRAFT state with draft number and totals","value":{"success":true,"data":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","invoice_number":null,"number":null,"type":"STANDARD","status":"DRAFT","issue_date":"2025-01-20","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321","email":"cliente@ejemplo.com","address":{"street":"Avenida Cliente","number":"456","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"}},"lines":[{"description":"Corporate website development","quantity":40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"taxable_base":1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":1815}],"totals":{"taxable_base":1500,"total_vat":315,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":1500,"amount":315}],"invoice_total":1815},"payment_info":{"method":"BANK_TRANSFER","payment_term_days":30},"notes":"Payment via bank transfer","verifactu":{"enabled":false},"created_at":"2025-01-20T10:30:00Z","updated_at":"2025-01-20T10:30:00Z"},"meta":{"timestamp":"2025-01-20T10:30:00Z","request_id":"c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f"}}},"exceeds_3000":{"summary":"Simplified invoice over 3,000€ (400)","value":{"success":false,"error":{"code":"SIMPLIFIED_INVOICE_EXCEEDS_LEGAL_LIMIT","message":"Simplified invoices cannot exceed 3,000€ (VAT included). Current total: 4,235€. Reduce the amount or issue an ordinary invoice.","details":{}},"meta":{"timestamp":"2025-02-03T12:00:00Z","request_id":"req_error_simpl001"}}},"conflict-409":{"summary":"Conflict (409)","description":"An equivalent resource already exists. `error.code` names the clash (here a customer with the same identifier); `details` is an empty object.","value":{"success":false,"error":{"code":"CLIENT_DUPLICATE","message":"A client with this NIF already exists","details":{}},"meta":{"timestamp":"2025-01-20T10:00:00Z","request_id":"d4d4d4d4-0004-4000-a000-000000000004"}}},"validation-422":{"summary":"Validation error (422)","description":"The body parses but one or more values are not acceptable. `details` is a flat map from the offending property (snake_case, nested paths joined with a dot) to a message; one entry per property.","value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"The provided data is not valid.","details":{"nif":"The field 'nif' cannot be empty","lines":"The field 'lines' cannot be empty","recipient.address.postal_code":"Contains invalid characters."}},"meta":{"timestamp":"2025-01-20T10:00:00Z","request_id":"c3c3c3c3-0003-4000-a000-000000000003"}}},"response_example":{"summary":"Simplified invoice with an identified recipient (422)","value":{"success":false,"error":{"code":"SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT","message":"BeeL. does not issue simplified invoices with an identified recipient (NIF or alternative identifier). Issue an ordinary invoice instead.","details":{}},"meta":{"timestamp":"2025-02-03T12:05:00Z","request_id":"req_error_simpl002"}}},"derived_unit_price_out_of_range_response":{"summary":"Derived unit price out of range (422)","value":{"success":false,"error":{"code":"LINE_UNIT_PRICE_OUT_OF_RANGE","message":"The unit price is out of the accepted range: the maximum is 99999999.9999 per unit","details":{"field":"lines[0]","max":99999999.9999,"derived_unit_price":9999999999}},"meta":{"timestamp":"2026-03-05T10:30:00Z","request_id":"b1b1b1b1-0001-4000-a000-000000000011"}}},"get-invoice-success":{"summary":"Get invoice (200)","description":"Single invoice retrieved with full details including its VeriFactu record (hash, QR) and PDF URL","value":{"success":true,"data":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","invoice_number":"A/2025/0042","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":42,"type":"STANDARD","status":"ISSUED","issue_date":"2025-01-20","due_date":"2025-02-20","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"},"email":"info@tuempresa.es","phone":"+34912345678"},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321","email":"cliente@ejemplo.com","address":{"street":"Avenida Cliente","number":"456","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"}},"lines":[{"description":"Corporate website development","quantity":40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"taxable_base":1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15,"line_total":1590}],"totals":{"taxable_base":1500,"total_vat":315,"total_irpf":225,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":1500,"amount":315}],"irpf_breakdown":[{"type":15,"base":1500,"amount":225}],"invoice_total":1590},"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"notes":"Payment via bank transfer","verifactu":{"enabled":true,"invoice_hash":"A7F3C9E2B1D4F8A6C3E9B2D5F1A8C4E7B9D2F5A1C8E4B7D3F9A2C6E1B5D8F4A7","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2025%2F0042&fecha=20-01-2025&importe=1590.00","registered_at":"2025-01-20T10:35:00Z","submission_status":"ACCEPTED"},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf","created_at":"2025-01-20T10:30:00Z","updated_at":"2025-01-20T10:35:00Z"},"meta":{"timestamp":"2025-01-20T11:00:00Z","request_id":"f4a5b6c7-d8e9-4f0a-1b2c-3d4e5f6a7b8c"}}},"generated-invoice":{"summary":"Stripe-generated invoice (200)","description":"A SIMPLIFIED invoice auto-generated from a Stripe Connect charge. Note the\n`metadata` block: BeeL writes system keys you can filter on — most usefully\n`external_customer_id` (the Stripe customer) and `external_payment_id`. Use\n`GET /v1/companies/{company_id}/invoices?metadata[external_customer_id]=cus_…` to\nlist a customer's full history.\nSee the \"Filtering by metadata\" guide for the full key list.\n","value":{"success":true,"data":{"id":"9d2fd4c2-29a1-4f65-90f7-78854adf69bc","invoice_number":"A/2026/0204","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":204,"type":"SIMPLIFIED","status":"PAID","issue_date":"2026-06-02","due_date":"2026-06-02","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"},"email":"info@tuempresa.es","phone":"+34912345678"},"recipient":{"legal_name":"Aleksei Borisov","email":"asbfox@gmail.com"},"lines":[{"description":"Online subscription","quantity":1,"unit":"service","unit_price":81.82,"discount_percentage":0,"taxable_base":81.82,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":0,"line_total":99}],"totals":{"taxable_base":81.82,"total_vat":17.18,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":81.82,"amount":17.18}],"irpf_breakdown":[],"invoice_total":99},"payment_info":{"method":"CARD"},"metadata":{"external_customer_id":"cus_ULGk8bzIr88aag","external_payment_id":"pi_3Tdpm4RzLnzHAY341P3TsOfk","payment_intent_id":"pi_3Tdpm4RzLnzHAY341P3TsOfk","charge_id":"ch_3Tdpm4RzLnzHAY341upZN1op","payment_provider":"STRIPE_CONNECT","stripe_fiscal_mode":"INCLUSIVE","original_amount":"99.00","original_currency":"eur","order_id":"1593","site_url":"https://superpole.es"},"verifactu":{"enabled":true,"invoice_hash":"A7F3C9E2B1D4F8A6C3E9B2D5F1A8C4E7B9D2F5A1C8E4B7D3F9A2C6E1B5D8F4A7","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2026%2F0204&fecha=02-06-2026&importe=99.00","registered_at":"2026-06-02T10:35:00Z","submission_status":"ACCEPTED"},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/9d2fd4c2-29a1-4f65-90f7-78854adf69bc/pdf","created_at":"2026-06-02T10:30:00Z","updated_at":"2026-06-02T10:35:00Z"},"meta":{"timestamp":"2026-06-02T11:00:00Z","request_id":"f4a5b6c7-d8e9-4f0a-1b2c-3d4e5f6a7b8c"}}},"update-invoice":{"summary":"Update draft invoice","description":"Update invoice in DRAFT state. Modify dates, lines, recipient, payment method","value":{"lines":[{"description":"Corporate website development - Updated","quantity":45,"unit":"hours","unit_price":40,"discount_percentage":5,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}},{"description":"Web maintenance - 3 months","quantity":3,"unit":"month","unit_price":150,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"payment_info":{"method":"DIRECT_DEBIT","iban":"ES9121000418450200051332"},"notes":"Invoice updated with new maintenance line. Payment via direct debit."}},"void-issued-by-error":{"summary":"Void invoice - issued by error","description":"Voiding an invoice that should not exist: an accidental duplicate, a test, or an operation that never took place. `issued_in_error` confirms it, and is required once the invoice has been emailed to the customer or paid.","value":{"reason":"Duplicado de la factura F-2026-0142, emitida dos veces por error","issued_in_error":true}},"corrective-total-r1":{"summary":"Total corrective R1 - Error founded in law","description":"R1 also covers an error founded in law, such as a wrong tax treatment. Rectifies everything still invoiced on the original (status VOIDED); issue the correct invoice afterwards.","value":{"rectification_type":"TOTAL","rectification_code":"R1","reason":"Spanish VAT was charged on a service not subject to it in Spain (Art. 69 LIVA): the invoice is rectified in full and reissued without VAT.","notes":"The correct invoice is issued separately, without Spanish VAT."}},"corrective-total-cancellation-r1":{"summary":"Total corrective R1 - Operation cancelled by agreement","description":"R1 covers the causes of Art. 80 Uno, Dos and Seis LIVA. An operation left without effect by agreement with the customer is Art. 80 Dos. Rectifies everything still invoiced on the original (status VOIDED).","value":{"rectification_type":"TOTAL","rectification_code":"R1","reason":"Order cancelled by agreement with the customer before delivery: the operation is left without effect (Art. 80 Dos LIVA).","circumstance_date":"2026-03-10","notes":"Cancellation agreed with the customer on 10/03/2026. No amount pending collection."}},"corrective-partial-r2":{"summary":"Partial corrective R2 - Customer insolvency","description":"R2 for a customer declared insolvent (Art. 80 Tres LIVA) who has not paid the VAT charged. The recipient must be established in Spain, or in another EU member state for insolvency proceedings declared there.","value":{"rectification_type":"PARTIAL","rectification_code":"R2","reason":"Customer declared insolvent by order of 10/02/2026 (Commercial Court No. 2 Madrid, case 123/2026): the unpaid part of the invoice is reduced under Art. 80 Tres LIVA.","circumstance_date":"2026-02-10","lines":[{"description":"Unpaid amount at the insolvency declaration","quantity":-16,"unit":"hours","unit_price":37.5,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"notes":"Credit communicated to the insolvency administrator."}},"corrective-partial-r3":{"summary":"Partial corrective R3 - Bad debt","description":"R3 for an uncollectable debt (Art. 80 Cuatro LIVA): one year since the tax accrued without payment, or six months for a business whose turnover the year before did not exceed 6,010,121.04 €, the debt claimed in court or by notarial demand and recorded in the VAT books. The API checks the six months from the original operation date.","value":{"rectification_type":"PARTIAL","rectification_code":"R3","reason":"Bad debt under Art. 80 Cuatro LIVA: more than six months since the tax accrued without collection, claimed by notarial demand.","circumstance_date":"2026-05-04","lines":[{"description":"Bad debt adjustment - unpaid amount","quantity":-40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"notes":"Notarial demand of 04/05/2026 (Protocol 456/2026). Operation of 15/10/2025."}},"r1-partial-discount":{"summary":"R1 corrective (partial) - post-issuance discount","description":"Partial rectification with negative quantity for discount adjustment","value":{"rectification_type":"PARTIAL","rectification_code":"R1","reason":"Descuento comercial post-emisión acordado con el cliente (5 horas no facturables)","lines":[{"description":"Ajuste por horas no facturables — Sprint mayo","quantity":-5,"unit":"hours","unit_price":75,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}],"options":{"issue_directly":true}}},"recipient-data-r4":{"summary":"R4 corrective - recipient's data recorded wrong","description":"Corrects the name, tax ID or address of the recipient when the invoice recorded them wrong (RD 1619/2012 art. 15.1; AEAT R4 for a non-monetary detail). PARTIAL, R4, no lines: the amounts do not change. For a registered customer, send its customer_id once its data are fixed.","value":{"rectification_type":"PARTIAL","rectification_code":"R4","reason":"The customer's tax ID was mistyped on the invoice","recipient":{"customer_id":"8f1e2a3b-4c5d-6e7f-8091-a2b3c4d5e6f7"}}},"r5-partial-simplified-return":{"summary":"R5 corrective (partial) - item returned on a simplified invoice","description":"A simplified invoice is always rectified with R5, whatever the cause. To give the customer a full invoice with their details instead, use the exchange of simplified invoices: that is not a corrective.","value":{"rectification_type":"PARTIAL","rectification_code":"R5","reason":"El cliente devolvió uno de los dos artículos del tique","lines":[{"description":"Devolución - camiseta talla M","quantity":-1,"unit_price":16.53,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}]}},"incompatible_series":{"summary":"Explicit series of the wrong type (422)","description":"An explicit `series_id` was sent, but that series is not typed for corrective invoices (e.g. an ordinary series). Pass a corrective series, or omit `series_id` to use the company default.","value":{"success":false,"error":{"code":"SERIES_INCOMPATIBLE_DOC_TYPE","message":"The selected series is not compatible with this document type. Please select a series of the correct type.","details":{"expected_document_type":"CORRECTIVE","defaults_status_endpoint":"GET /v1/companies/{company_id}/series/defaults"}},"meta":{"timestamp":"2025-02-01T10:20:00Z","request_id":"b2b2b2b2-0810-4000-a000-000000000422"}}},"response_example-2":{"summary":"Invoice without lines (422)","value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invoice must have at least one line item","details":{"lines":["Array must contain at least 1 element(s)"]}},"meta":{"timestamp":"2025-01-26T11:15:00Z","request_id":"b1b1b1b1-0001-4000-a000-000000000010"}}},"response_example-3":{"summary":"Recipient tax id is not valid (422)","value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid NIF format","details":{"nif":["Invalid Spanish NIF format: B123INVALID"]}},"meta":{"timestamp":"2025-01-26T10:45:00Z","request_id":"req_error_nif001"}}},"response_example-4":{"summary":"Required fields missing (422)","value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Multiple validation errors found","details":{"issue_date":["Field is required"],"recipient":{"nif":["Either 'nif' or 'alternative_id' must be provided"],"address":{"postal_code":["Field is required"],"city":["Field is required"],"province":["Field is required"]}},"lines":{"0":{"unit_price":["Field is required"]}}}},"meta":{"timestamp":"2025-01-26T11:30:00Z","request_id":"req_error_validation001"}}},"simple_send":{"summary":"Simple send (uses customer email if recipients not provided)","value":{"subject":"Invoice A/2025/0042 from Tu Empresa SL","message":"Dear customer,\n\nPlease find attached invoice A/2025/0042 for services rendered.\nPlease make payment before the due date.\n\nKind regards,\nTu Empresa SL\n"}},"multiple_recipients":{"summary":"Multiple recipients with CC","value":{"recipients":["cliente@ejemplo.com","contabilidad@ejemplo.com","administracion@ejemplo.com"],"cc":["archivo@tuempresa.com"],"subject":"Invoice A/2025/0042 - January services","message":"Invoice for services rendered during January 2025."}},"issue-success":{"summary":"Invoice issued (200)","description":"State changed to ISSUED. Final number assigned, VeriFactu record generated with its hash, PDF generated","value":{"success":true,"data":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","invoice_number":"A/2025/0042","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":42,"type":"STANDARD","status":"ISSUED","issue_date":"2025-01-20","due_date":"2025-02-20","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321"},"lines":[{"description":"Corporate website development","quantity":40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"taxable_base":1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":1815}],"totals":{"taxable_base":1500,"total_vat":315,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":1500,"amount":315}],"invoice_total":1815},"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"verifactu":{"enabled":true,"invoice_hash":"A7F3C9E2B1D4F8A6C3E9B2D5F1A8C4E7B9D2F5A1C8E4B7D3F9A2C6E1B5D8F4A7","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2025%2F0042&fecha=20-01-2025&importe=1815.00","registered_at":"2025-01-20T10:35:00Z","submission_status":"ACCEPTED"},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf","created_at":"2025-01-20T10:30:00Z","updated_at":"2025-01-20T10:35:00Z"},"meta":{"timestamp":"2025-01-20T10:35:00Z","request_id":"e3f4a5b6-c7d8-4e9f-0a1b-2c3d4e5f6a7b"}}},"corrective-created-success":{"summary":"Corrective invoice created (201)","description":"New corrective invoice created. Original invoice marked as RECTIFIED/VOIDED","value":{"success":true,"data":{"id":"b8a4d0f3-c2e5-49b7-d4f0-c3f2b9e6d1f5","invoice_number":"A/2025/0043-R","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":43,"type":"CORRECTIVE","status":"ISSUED","issue_date":"2025-01-25","rectified_invoice_id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","rectification_reason":"Partial correction for bad debt under Art. 80 Cuatro LIVA","rectification_type":"PARTIAL","rectification_code":"R3","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321"},"lines":[{"description":"Bad debt adjustment - Full cancellation of outstanding amount","quantity":-40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"taxable_base":-1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":-1815}],"totals":{"taxable_base":-1500,"total_vat":-315,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":-1500,"amount":-315}],"invoice_total":-1815},"verifactu":{"enabled":true,"invoice_hash":"B8A4D0F3C2E549B7D4F0C3F2B9E6D1F5A8C2E7B0D4F8C3A6B1E9D2F7A5C8E3B0","qr_url":"https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B12345674&numserie=A%2F2025%2F0043-R&fecha=25-01-2025&importe=-1815.00","registered_at":"2025-01-25T11:00:00Z","submission_status":"ACCEPTED"},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/b8a4d0f3-c2e5-49b7-d4f0-c3f2b9e6d1f5/pdf","created_at":"2025-01-25T11:00:00Z","updated_at":"2025-01-25T11:00:00Z"},"meta":{"timestamp":"2025-01-25T11:00:00Z","request_id":"a5b6c7d8-e9f0-4a1b-2c3d-4e5f6a7b8c9d"}}},"original_mixed_irpf":{"summary":"Original invoice with mixed IRPF rates (422)","description":"A `PARTIAL` corrective left `irpf_rate` off at least one line and the original invoice withholds different rates on different lines, so there is no single rate to inherit. Send `irpf_rate` explicitly on every line of the corrective invoice — `0` included, which means \"no withholding\".","value":{"success":false,"error":{"code":"CORRECTIVE_ORIGINAL_MIXED_IRPF","message":"The original invoice applies different IRPF withholding rates per line, so there is none to inherit. Set irpf_rate on every line of the corrective invoice."},"meta":{"timestamp":"2025-02-01T10:25:00Z","request_id":"b2b2b2b2-1435-4000-a000-000000001435"}}},"original_mixed_surcharge":{"summary":"Original invoice with a mixed equivalence surcharge regime (422)","description":"A `PARTIAL` corrective left `equivalence_surcharge_rate` off at least one line and the original invoice applies the surcharge on some lines but not others, so there is no regime to inherit. Send `equivalence_surcharge_rate` explicitly on every line of the corrective invoice. Note that an original at 21% and 10% carrying 5.2 and 1.4 is *not* mixed: that is one regime applied to two VAT rates, and it is inherited without error.","value":{"success":false,"error":{"code":"CORRECTIVE_ORIGINAL_MIXED_SURCHARGE","message":"The original invoice applies the equivalence surcharge on some lines but not others, so there is no regime to inherit. Set equivalence_surcharge_rate on every line of the corrective invoice."},"meta":{"timestamp":"2025-02-01T10:30:00Z","request_id":"b2b2b2b2-1435-4000-a000-000000001436"}}},"convert-proforma-draft":{"summary":"Convert into a draft invoice","description":"The new invoice stays as a draft","value":{"issue":false}},"convert-proforma-issue":{"summary":"Convert and issue","description":"The new invoice is issued in the same call","value":{"issue":true}},"mark-paid":{"summary":"Mark invoice as paid","description":"Transition to PAID state. Records payment date and method","value":{"status":"PAID","payment_date":"2025-02-05","payment_method":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332"}}},"mark-paid-success":{"summary":"Invoice marked as paid (200)","description":"State changed to PAID. Payment date, method, and reference recorded","value":{"success":true,"data":{"id":"f47ac10b-58cc-4372-a567-0e02b2c3d479","invoice_number":"A/2025/0042","series":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A"},"number":42,"type":"STANDARD","status":"PAID","issue_date":"2025-01-20","due_date":"2025-02-20","payment_date":"2025-02-05","sent_at":"2025-01-25T09:00:00Z","paid_at":"2025-02-05T14:30:00Z","issuer":{"legal_name":"Tu Empresa SL","nif":"B12345674","address":{"street":"Calle Ejemplo","number":"123","postal_code":"28001","city":"Madrid","province":"Madrid","country":"España"}},"recipient":{"customer_id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Cliente Ejemplo SL","nif":"B87654321"},"lines":[{"description":"Corporate website development","quantity":40,"unit":"hours","unit_price":37.5,"discount_percentage":0,"taxable_base":1500,"main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"line_total":1815}],"totals":{"taxable_base":1500,"total_vat":315,"total_irpf":0,"total_equivalence_surcharge":0,"vat_breakdown":[{"type":21,"base":1500,"amount":315}],"invoice_total":1815},"payment_info":{"method":"BANK_TRANSFER","iban":"ES9121000418450200051332","payment_term_days":30},"pdf_download_url":"/v1/companies/7c9e6679-7425-40de-944b-e07fc1f90ae7/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf","created_at":"2025-01-20T10:30:00Z","updated_at":"2025-02-05T14:30:00Z"},"meta":{"timestamp":"2025-02-05T14:30:00Z","request_id":"d2e3f4a5-b6c7-4d8e-9f0a-1b2c3d4e5f6a"}}},"schedule-draft":{"summary":"Leave a draft ready on a date","description":"On the scheduled date the invoice is prepared as a draft for review","value":{"scheduled_for":"2026-10-15","generation_mode":"DRAFT"}},"schedule-issue-and-send":{"summary":"Issue and send on a date","description":"On the scheduled date the invoice is issued and emailed","value":{"scheduled_for":"2026-10-31","generation_mode":"ISSUE_AND_SEND"}},"create-monthly":{"summary":"Monthly maintenance fee","description":"Invoices a fixed fee on the 1st of every month, emailed to the customer when it is issued","value":{"name":"Mantenimiento web mensual","frequency":"MONTHLY","day_of_month":1,"start_date":"2026-10-01","end_date":"2027-09-30","series_id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","invoice_type":"STANDARD","customer_id":"123e4567-e89b-12d3-a456-426614174000","lines":[{"description":"Mantenimiento mensual de la web corporativa","quantity":1,"unit":"mes","unit_price":450,"tax_type":"IVA","vat_rate":21,"regime_key":"01","irpf_rate":15}],"payment_method":"BANK_TRANSFER","payment_iban":"ES9121000418450200051332","payment_term_days":15,"send_automatically":true,"email_configuration":{"recipients":["facturas@example.com"],"subject":"Factura mensual de mantenimiento","message":"Adjuntamos la factura de este mes."}}},"patch-schedule":{"summary":"Change the cadence and end date","description":"Moves the template to a quarterly cadence on the 5th and sets an end date; every other field is kept","value":{"frequency":"QUARTERLY","day_of_month":5,"end_date":"2027-12-31"}},"patch-lines":{"summary":"Replace the lines","description":"Sends the new price of the fee; the lines array replaces the previous one as a whole","value":{"lines":[{"description":"Mantenimiento mensual de la web corporativa","quantity":1,"unit":"mes","unit_price":495,"tax_type":"IVA","vat_rate":21,"regime_key":"01","irpf_rate":15}]}},"status-paused":{"summary":"Pause","description":"Stops automatic generation and keeps the schedule","value":{"status":"PAUSED"}},"status-active":{"summary":"Resume","description":"Resumes a paused schedule","value":{"status":"ACTIVE"}},"status-completed":{"summary":"End for good","description":"Ends the schedule; it cannot be resumed afterwards","value":{"status":"COMPLETED"}},"derivation-from-invoice":{"summary":"Repeat an invoice every month","description":"Lines, recipient, series and payment data are taken from the source invoice","value":{"from_invoice_id":"9b2f6c1e-4d8a-4f3b-a1c2-5e6d7f8a9b0c","name":"Cuota mensual de asesoría","frequency":"MONTHLY","day_of_month":1,"start_date":"2026-11-01","send_automatically":false}},"list-customers-success":{"summary":"List customers (200)","description":"Customer list with pagination","value":{"success":true,"data":{"customers":[{"id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Tech Solutions SL","nif":"B12345674","email":"admin@techsolutions.com","phone":"+34912345678","address":{"street":"Calle Mayor","number":"15","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"},"active":true,"created_at":"2024-06-15T10:30:00Z","updated_at":"2025-01-10T14:00:00Z"},{"id":"789abc12-34de-56f7-8901-234567890abc","legal_name":"María García López","nif":"12345678Z","email":"maria.garcia@email.com","phone":"+34654321987","address":{"street":"Avenida de la Constitución","number":"25","floor":"3º","door":"B","postal_code":"41001","city":"Sevilla","province":"Sevilla","country":"España"},"active":true,"created_at":"2024-08-20T09:00:00Z","updated_at":"2024-12-01T11:30:00Z"},{"id":"456def78-90ab-12cd-3456-78ef01234567","legal_name":"Distribuidora Levante SA","nif":"A22222222","email":"contabilidad@distribuidoralevante.es","phone":"+34963456789","address":{"street":"Polígono Industrial Norte","number":"7","postal_code":"46007","city":"Valencia","province":"Valencia","country":"España"},"active":false,"created_at":"2024-03-10T16:45:00Z","updated_at":"2024-11-20T08:00:00Z"}],"pagination":{"current_page":1,"items_per_page":20,"total_items":3,"total_pages":1,"has_next":false,"has_previous":false}},"meta":{"timestamp":"2025-01-20T12:00:00Z","request_id":"550e8400-e29b-41d4-a716-446655440000"}}},"company-customer":{"summary":"Spanish company (SL/SA)","description":"Spanish company with CIF format: Letter + 8 digits (e.g., B12345674)","value":{"legal_name":"Tech Solutions SL","nif":"B12345674","email":"admin@techsolutions.com","phone":"+34912345678","address":{"street":"Calle Mayor","number":"123","floor":"2º B","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"},"notes":"Cliente prioritario - Pago puntual"}},"freelancer-customer":{"summary":"Self-employed individual","description":"Self-employed with personal NIF. Format: 8 digits + letter (e.g., 12345678Z)","value":{"legal_name":"María García López","nif":"12345678Z","email":"maria.garcia@email.com","phone":"+34654321987","address":{"street":"Avenida de la Constitución","number":"45","floor":"3º A","postal_code":"41001","city":"Sevilla","province":"Sevilla","country":"España"},"notes":"Diseñadora gráfica freelance"}},"eu_customer":{"summary":"EU Customer with VAT","value":{"legal_name":"Tech Europe GmbH","alternative_id":{"type":"02","number":"DE123456789","country_code":"DE"},"email":"invoices@techeurope.de","phone":"+49301234567","address":{"street":"Hauptstraße","number":"456","postal_code":"10115","city":"Berlin","province":"Berlin","country":"Alemania"},"notes":"Cliente intracomunitario - Aplicar inversión del sujeto pasivo"}},"us_customer":{"summary":"US Customer with EIN","value":{"legal_name":"American Tech Corp","alternative_id":{"type":"06","number":"12-3456789","country_code":"US"},"email":"billing@americantech.com","phone":"+14155551234","address":{"street":"Main Street","number":"123","floor":"Suite 500","postal_code":"94102","city":"San Francisco","province":"California","country":"Estados Unidos"},"notes":"Cliente extracomunitario - Factura de exportación"}},"spanish-business":{"summary":"Spanish business customer (ACCIONA)","description":"Customer creation for Spanish company with full address and NIF","value":{"legal_name":"ACCIONA SA","nif":"A08001851","address":{"street":"Avenida de Europa","number":"18","postal_code":"28108","city":"Alcobendas","province":"Madrid","country":"España","country_code":"ES"}}},"created-success-2":{"summary":"Customer created (201)","description":"Customer created successfully with generated ID and initial statistics","value":{"success":true,"data":{"id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Tech Solutions SL","nif":"B12345674","email":"admin@techsolutions.com","phone":"+34912345678","address":{"street":"Calle Mayor","number":"123","floor":"2º B","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"},"notes":"Cliente prioritario - Pago puntual","active":true,"created_at":"2025-01-20T10:00:00Z","updated_at":"2025-01-20T10:00:00Z"},"meta":{"timestamp":"2025-01-20T10:00:00Z","request_id":"c1c1c1c1-0001-4000-a000-000000000001"}}},"get-customer-success":{"summary":"Get customer (200)","description":"Single customer retrieved with full details","value":{"success":true,"data":{"id":"123e4567-e89b-12d3-a456-426614174000","legal_name":"Tech Solutions SL","nif":"B12345674","email":"admin@techsolutions.com","phone":"+34912345678","address":{"street":"Calle Mayor","number":"123","floor":"2º B","postal_code":"28013","city":"Madrid","province":"Madrid","country":"España"},"notes":"Cliente prioritario - Pago puntual","active":true,"created_at":"2024-03-15T09:00:00Z","updated_at":"2025-01-20T10:00:00Z"},"meta":{"timestamp":"2025-01-20T11:00:00Z","request_id":"b6c7d8e9-f0a1-4b2c-3d4e-5f6a7b8c9d0e"}}},"create-multiple":{"summary":"Create multiple customers (bulk)","description":"Atomic bulk creation with dry-run mode, duplicate detection, and validation","value":{"customers":[{"legal_name":"Construcciones García SL","nif":"B11111111","email":"info@construccionesgarcia.com","address":{"street":"Calle Albañil","number":"15","postal_code":"28010","city":"Madrid","province":"Madrid","country":"España"}},{"legal_name":"Pedro Martínez Ruiz","nif":"11111111A","email":"pedro.martinez@email.com","address":{"street":"Avenida Principal","number":"200","postal_code":"08015","city":"Barcelona","province":"Barcelona","country":"España"}},{"legal_name":"Distribuidora Levante SA","nif":"A22222222","email":"contabilidad@distribuidoralevante.es","address":{"street":"Polígono Industrial","number":"Nave 7","postal_code":"46015","city":"Valencia","province":"Valencia","country":"España"}}]}},"bulk-created-success":{"summary":"Bulk customers created (200)","description":"All-or-nothing: the batch is only persisted when every row can be imported, so `statistics.imported` either matches `total_processed` or is `0`. Each row is still reported individually, with the `customer_id` of what it created.","value":{"success":true,"data":{"metadata":{"total_customers":3,"is_dry_run":false,"processing_time_ms":180,"source_type":"BULK_JSON"},"customers_validation":[{"index":0,"status":"VALID","customer_id":"bbb222cc-33dd-44ee-5566-77ff88990011","customer":{"legal_name":"Construcciones García SL","nif":"B11111111"},"errors":[],"warnings":[]},{"index":1,"status":"VALID","customer_id":"ccc333dd-44ee-55ff-6677-88990011aabb","customer":{"legal_name":"Pedro Martínez Ruiz","nif":"11111111A"},"errors":[],"warnings":[]},{"index":2,"status":"VALID","customer_id":"ddd444ee-55ff-66aa-7788-990011aabbcc","customer":{"legal_name":"Distribuidora Levante SA","nif":"A22222222"},"errors":[],"warnings":[]}],"statistics":{"total_processed":3,"valid":3,"with_warnings":0,"with_errors":0,"duplicates":0,"invalid_nifs":0,"importable":3,"not_importable":0,"imported":3,"success_rate":1}},"meta":{"timestamp":"2025-02-08T10:00:00Z","request_id":"req_bulk_001"}}},"update_address":{"summary":"Update address","value":{"address":{"street":"Calle Nueva","number":"456","floor":"5º A","postal_code":"28015","city":"Madrid","province":"Madrid","country":"España"},"notes":"Cliente se ha mudado a nueva oficina"}},"update_contact":{"summary":"Update contact","value":{"email":"nuevo-email@techsolutions.com","phone":"+34912345999","notes":"Cliente prioritario - Pago puntual. Nuevo contacto desde enero 2025."}},"deactivate_customer":{"summary":"Deactivate customer","value":{"active":false,"notes":"Cliente inactivo desde febrero 2025 - Sin actividad comercial"}},"list-products-success":{"summary":"List products (200)","description":"Product catalog list with pagination metadata","value":{"success":true,"data":{"products":[{"id":"550e8400-e29b-41d4-a716-446655440001","code":"SRV-001","name":"Consultoría estratégica","description":"Servicios de consultoría estratégica por hora","category":"CONSULTING","default_price":125,"unit":"hora","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"active":true,"created_at":"2025-01-10T10:00:00Z","updated_at":"2025-01-10T10:00:00Z"},{"id":"550e8400-e29b-41d4-a716-446655440002","code":"PROD-LAPTOP-001","name":"Ordenador portátil HP ProBook 450 G10","description":"Incluye garantía de 2 años","category":"PRODUCT","default_price":850,"unit":"unidad","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"active":true,"created_at":"2025-01-15T14:30:00Z","updated_at":"2025-01-15T14:30:00Z"},{"id":"550e8400-e29b-41d4-a716-446655440003","code":"SRV-002","name":"Diseño gráfico - Proyecto completo","category":"SERVICE","default_price":500,"unit":"proyecto","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"active":false,"created_at":"2025-01-08T09:00:00Z","updated_at":"2025-02-01T11:00:00Z"}],"pagination":{"current_page":1,"items_per_page":20,"total_items":3,"total_pages":1,"has_next":false,"has_previous":false}},"meta":{"timestamp":"2025-02-09T11:00:00Z","request_id":"req_list_productos_001"}}},"physical_product":{"summary":"Physical product","value":{"code":"PROD-LAPTOP-001","name":"Ordenador portátil HP ProBook 450 G10","description":"Incluye garantía de 2 años","category":"PRODUCT","default_price":850,"unit":"unidad","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}},"service_product":{"summary":"Service product","value":{"code":"SRV-001","name":"Consultoría estratégica","description":"Servicios de consultoría estratégica por hora","category":"CONSULTING","default_price":125,"unit":"hora","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}},"created-success-3":{"summary":"Product created (201)","description":"Product created successfully in catalog with default tax configuration","value":{"success":true,"data":{"id":"550e8400-e29b-41d4-a716-446655440000","code":"SRV-001","name":"Consultoría estratégica","description":"Servicios de consultoría estratégica por hora","category":"CONSULTING","default_price":125,"unit":"hora","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"active":true,"created_at":"2025-02-09T10:00:00Z","updated_at":"2025-02-09T10:00:00Z"},"meta":{"timestamp":"2025-02-09T10:00:00Z","request_id":"req_producto_001"}}},"create-multiple-2":{"summary":"Create several products","description":"Two catalogue items in one call; each one succeeds or fails on its own","value":{"products":[{"code":"SRV-002","name":"Auditoría SEO","description":"Informe de posicionamiento y plan de acción","category":"CONSULTING","default_price":600,"unit":"servicio","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"irpf_rate":15},{"code":"FORM-001","name":"Taller de marketing digital","category":"TRAINING","default_price":90,"unit":"hora","main_tax":{"type":"IVA","percentage":21,"regime_key":"01"}}]}},"update_price":{"summary":"Update price","value":{"default_price":135,"description":"Servicios de consultoría estratégica por hora - Precio actualizado para 2025"}},"update_tax":{"summary":"Update tax configuration","value":{"main_tax":{"type":"IVA","percentage":10,"regime_key":"01"}}},"deactivate_product":{"summary":"Deactivate product","value":{"active":false}},"member-role-admin":{"summary":"Promote to admin","value":{"account_role":"ADMIN"}},"member-grant-operate":{"summary":"Let the member operate the company","value":{"access_level":"OPERATE"}},"set-owner":{"summary":"Transfer ownership to a member","value":{"member_id":"3f2a1b4c-5d6e-4f70-8a9b-0c1d2e3f4a5b"}},"invitation-member":{"summary":"Invite a member with access to one company","description":"BeeL emails the acceptance link; the member can operate one company","value":{"invited_email":"ana.garcia@example.com","account_role":"MEMBER","send_email":true,"grants":[{"company_id":"550e8400-e29b-41d4-a716-446655440000","access_level":"OPERATE"}]}},"invitation-admin":{"summary":"Invite an admin","description":"Admins reach every company, so no grants are sent; you deliver the token yourself","value":{"invited_email":"luis.martin@example.com","account_role":"ADMIN","send_email":false,"grants":[]}},"provision-with-company":{"summary":"Provision an account with its first company","description":"The account is created with its holder and fiscal profile; you keep operating it","value":{"email":"hola@estudio-ejemplo.example.com","display_name":"Estudio Ejemplo","external_ref":"cliente-4821","language":"es","access_level":"OPERATE","tax_profile":{"nif":"12345678Z","legal_name":"María López Fernández","entity_type":"INDIVIDUAL","address":{"street":"Calle Mayor","number":"15","floor":"2","door":"B","postal_code":"28013","city":"Madrid","province":"Madrid","country_code":"ES"},"default_main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"default_irpf_rate":15},"send_email":false}},"provision-minimal":{"summary":"Provision an account without a holder yet","description":"The holder is added later, when the claim token is issued with their email","value":{"display_name":"Taller Ejemplo","external_ref":"cliente-4822"}},"access-level-view":{"summary":"Downgrade to read-only access","value":{"access_level":"VIEW"}},"claim-token-new-holder":{"summary":"Name the holder of an account that has none","value":{"email":"maria.lopez@example.com","language":"es"}},"create-individual":{"summary":"Self-employed person, activated in Test","description":"Seeds the three default series with the given numbering","value":{"nif":"12345678Z","legal_name":"María López Fernández","entity_type":"INDIVIDUAL","address":{"street":"Calle Mayor","number":"15","floor":"2","door":"B","postal_code":"28013","city":"Madrid","province":"Madrid","country_code":"ES"},"default_main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"default_irpf_rate":15,"aeat_environment":"TEST","activate":true,"numbering":{"code":"F","format":"{CODIGO}-{YYYY}-{NUM:4}","counter_reset":"ANNUAL","initial_number":1}}},"create-legal-entity":{"summary":"Company with a legal representative, not activated yet","description":"Created switched off; activate it later with POST /v1/companies/{company_id}/activations","value":{"nif":"B12345674","legal_name":"Ejemplo Soluciones SL","trade_name":"Ejemplo Soluciones","entity_type":"LEGAL_ENTITY","legal_form":"SL","address":{"street":"Calle Mayor","number":"15","postal_code":"28013","city":"Madrid","province":"Madrid","country_code":"ES"},"legal_representative":{"full_name":"Jorge Ruiz Sánchez","nif":"87654321X","address":{"street":"Calle Mayor","number":"15","floor":"2","door":"B","postal_code":"28013","city":"Madrid","province":"Madrid","country_code":"ES"}},"activate":false}},"patch-contact-and-bank":{"summary":"Update contact and bank details","description":"Only the fields sent change","value":{"phone":"+34 910 123 456","email":"facturacion@example.com","default_iban":"ES9121000418450200051332","default_swift":"CAIXESBBXXX","account_holder":"María López Fernández","default_payment_term":30}},"invoice-customization":{"summary":"Set template, colour and languages","value":{"invoice_template_type":"PROFESSIONAL_SERVICE","invoice_accent_color":"#1F6FEB","invoice_language":"es","email_language":"es"}},"verifactu-enable":{"summary":"Turn VeriFactu on","value":{"enabled":true}},"verifactu-disable":{"summary":"Turn VeriFactu off","description":"Not accepted in Test, where VeriFactu is always on","value":{"enabled":false}},"tax-professional":{"summary":"Professional with 21% VAT and 15% IRPF","value":{"default_main_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"apply_irpf":true,"default_irpf_rate":15,"apply_equivalence_surcharge":false,"default_payment_method":"BANK_TRANSFER","payment_term_days":30}},"tax-retailer":{"summary":"Retailer under the equivalence surcharge","value":{"apply_equivalence_surcharge":true,"default_equivalence_surcharge":5.2,"apply_irpf":false}},"main_series":{"summary":"Main series","value":{"code":"A","name":"Serie Principal","document_type":"STANDARD","description":"Default series for standard invoices","format":"{CODIGO}/{YYYY}/{NUM:4}","counter_reset":"ANNUAL","initial_number":1,"default_series":true}},"international_series":{"summary":"International series","value":{"code":"INT","name":"Serie Internacional","document_type":"STANDARD","description":"Invoices for foreign customers","format":"{CODIGO}/{YYYY}/{NUM:4}","counter_reset":"ANNUAL","initial_number":1,"default_series":false}},"created-success-4":{"summary":"Series created (201)","description":"Invoice series created with configuration and next number","value":{"success":true,"data":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","code":"A","name":"Serie Principal","document_type":"STANDARD","description":"Default series for standard invoices","format":"{CODIGO}/{YYYY}/{NUM:4}","counter_reset":"ANNUAL","initial_number":1,"default_series":true,"active":true,"created_at":"2025-02-09T11:00:00Z","updated_at":"2025-02-09T11:00:00Z"},"meta":{"timestamp":"2025-02-09T11:00:00Z","request_id":"req_serie_001"}}},"update_description":{"summary":"Update description","value":{"name":"Serie Principal Actualizada","description":"Default series for domestic standard invoices - Updated 2025"}},"change_format":{"summary":"Change numbering format","value":{"format":"{YYYY}-{CODIGO}-{NUM:4}"}},"initiate-stripe":{"summary":"Connect a Stripe account","value":{"provider":"stripe","return_url":"https://app.example.com/integrations/stripe/done"}},"update-settings":{"summary":"Invoice payments automatically, skipping refunds","description":"Only the fields sent change","value":{"auto_invoice_enabled":true,"event_source":"PAYMENTS","auto_create_customer":true,"send_invoice_by_email":true,"tax_inclusive_tax":{"type":"IVA","percentage":21,"regime_key":"01"},"filter_config":{"min_amount":1,"disabled_categories":["REFUND"]}}},"patch-events":{"summary":"Change the subscribed events","description":"The events array replaces the previous list","value":{"events":["invoice.issued","invoice.voided","verifactu.status.updated"]}},"patch-disable":{"summary":"Pause deliveries","value":{"active":false}}},"headers":{"CreatedResourceLocation":{"description":"URI of the created resource — its canonical GET (`/v1/companies/{company_id}/...` or `/v1/accounts/{account_id}/...`).","schema":{"type":"string","format":"uri"}}}},"x-scope-catalog":[{"name":"invoices:read","grants":"Read invoices"},{"name":"invoices:write","grants":"Create, update and delete invoices"},{"name":"customers:read","grants":"Read customers"},{"name":"customers:write","grants":"Create, update and delete customers"},{"name":"products:read","grants":"Read products"},{"name":"products:write","grants":"Create, update and delete products"},{"name":"members:read","grants":"Read the account's members"},{"name":"members:write","grants":"Change the role of and remove the account's members"},{"name":"accounts:read","grants":"Read the accounts you manage and their provisioning usage (privileged)"},{"name":"accounts:write","grants":"Provision accounts on behalf of their holders (privileged)"},{"name":"account:admin","grants":"Manage the account's own credentials and billing (privileged)"},{"name":"webhooks:read","grants":"Read webhook subscriptions and their deliveries"},{"name":"webhooks:write","grants":"Create, update and delete webhook subscriptions, rotate secrets and retry deliveries"},{"name":"configuration:read","grants":"Read configuration (VeriFactu, taxes, preferences)"},{"name":"configuration:write","grants":"Update configuration (VeriFactu, taxes, preferences)"},{"name":"series:read","grants":"Read invoice series"},{"name":"series:write","grants":"Create, update and delete invoice series"},{"name":"nif:validate","grants":"Validate NIFs against AEAT"},{"name":"companies:read","grants":"Read companies"},{"name":"companies:write","grants":"Manage companies"},{"name":"companies:list","grants":"List the account's companies (NIFs)"},{"name":"payment-connections:read","grants":"Read the payment connections of a managed NIF"},{"name":"payment-connections:write","grants":"Connect and disconnect payment providers for a managed NIF"},{"name":"emails:read","grants":"Read the email delivery history"},{"name":"logs:read","grants":"Read the API request logs. Its reach is the PERSON, not the key: a credential with this scope reads the requests — bodies and headers included — made with ANY of your API keys in that environment, including your privileged ones. It never crosses to another user or another account."}],"x-tagGroups":[{"name":"Invoices","x-icon":"FileText","tags":["CompanyInvoices","CompanyInvoiceLifecycle","CompanyInvoiceDelivery","Invoices","InvoiceLifecycle","InvoiceDelivery"]},{"name":"Proforma","x-icon":"FileClock","tags":["CompanyProforma","Proforma"]},{"name":"Customers","x-icon":"Users","tags":["CompanyCustomers","CompanyCustomerImports","Templates","Customers","Customer Import","Customer Templates"]},{"name":"Products","x-icon":"Package","tags":["CompanyProducts","Products"]},{"name":"Members","x-icon":"Users","tags":["Members"]},{"name":"Invitations","x-icon":"Mail","tags":["Invitations"]},{"name":"Accounts","x-icon":"Building2","tags":["Accounts","AccountImports","Provisioning"]},{"name":"Invoice Series","x-icon":"List","tags":["CompanySeries","InvoiceSeries"]},{"name":"Tax Configuration","x-icon":"Calculator","tags":["CompanyTaxConfiguration","TaxTypes","ConfigurationTax"]},{"name":"VeriFactu","x-icon":"ShieldCheck","tags":["CompanyVeriFactuConfiguration","ConfigurationVeriFactu"]},{"name":"Preferences","x-icon":"Settings","tags":["ConfigurationPreferences"]},{"name":"Invoice Customization","x-icon":"Palette","tags":["InvoiceCustomization"]},{"name":"NIF Validation","x-icon":"Search","tags":["NIF"]},{"name":"Companies","x-icon":"Building2","tags":["CompanyRepresentation","AccountCompanies","Company","CompanyActivations","PublicCompanyRepresentations","CompanyBranding"]},{"name":"Recurring Invoices","x-icon":"Repeat","tags":["CompanyRecurringInvoices","RecurringInvoices"]},{"name":"Payment Connections","x-icon":"CreditCard","tags":["CompanyPaymentConnections"]},{"name":"Payment Events","x-icon":"Receipt","tags":["CompanyPaymentEvents"]},{"name":"Webhooks","x-icon":"Webhook","tags":["AccountWebhooks","Webhooks"]},{"name":"Request Logs","x-icon":"ScrollText","tags":["AccountRequestLogs","RequestLogs"]},{"name":"Emails","x-icon":"Mail","tags":["Emails","AccountEmails"]},{"name":"Identity","x-icon":"Fingerprint","tags":["Me","Identity"]},{"name":"Fiscal Summary","x-icon":"Calculator","tags":["CompanyFiscalSummary"]}]}