---
title: "Update VeriFactu configuration"
method: PUT
path: "/v1/configuration/verifactu"
tags: ["ConfigurationVeriFactu"]
deprecated: true
---

# Update VeriFactu configuration

`PUT /v1/configuration/verifactu`

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/configuration/verifactu`
- Auth: HTTP bearer

> **Deprecated.**

**Deprecated.** Use `PUT /v1/companies/{company_id}/verifactu-configuration`, which
behaves identically.

Updates the VeriFactu configuration of the company in focus.

- **Writable fields:** only `enabled`. The rest of the returned
  configuration (`status`, `signed`, `activated`, `nif_status`) is resolved server-side.
- **Full replacement:** `enabled` is required — this PUT replaces the whole state, it is
  not a partial merge, so omitting it is a client error and not a silent `false`.
- **Turning it on registers the NIF for VeriFactu submission in the same call**,
  atomically: if the registration is refused nothing is persisted and the response carries
  the reason. In Live it requires a signed and validated AEAT representation first, or
  `422 VERIFACTU_REPRESENTATION_REQUIRED`.
- **Sandbox is always on:** `enabled: false` there answers `422`
  `VERIFACTU_ALWAYS_ON_IN_SANDBOX`.

## Turning it off

Setting `enabled` to false stops sending invoices to the AEAT and starts deregistering
the NIF from VeriFactu submission. It does **not** deactivate the
NIF: the activation is a fact of its own for the (company, environment) pair, so issuing
carries on and `issuing-readiness` stays `ready`. Releasing the NIF is always
`DELETE /v1/companies/{company_id}/activations`, with its own guarantees.

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

## Request body

- UpdateVeriFactuConfigurationRequest: Request body of `PUT /v1/configuration/verifactu`. Carries the **only writable field**; everything else in `VeriFactuConfiguration` is resolved server-side. `enabled` is required and has **no default**: this PUT replaces the whole state, so omitting it is a client error, not a silent `false`. A `default:` here would be materialized in the generated DTO and would satisfy the `@NotNull` before validation could tell "not sent" from "sent as false".
  - `enabled` boolean, required: Whether this tax ID is under the VeriFactu regime in the environment of the request. It is a fact about the **taxpayer**, not about a document: while it is on, *every* invoice of this tax ID is registered with the AEAT; while it is off, none is. There is no per-invoice choice. Turning it on **registers the tax ID for VeriFactu submission in the same call**, atomically: if the registration is refused, nothing is persisted and the response carries the reason. In Live it requires a signed and validated AEAT representation first (`VERIFACTU_REPRESENTATION_REQUIRED`). In **sandbox VeriFactu is always on and cannot be turned off** (`VERIFACTU_ALWAYS_ON_IN_SANDBOX`): nothing there reaches the real AEAT.

## Response `200`

Configuration updated successfully

- object: The envelope every successful JSON response of the BeeL. API is wrapped in. There are no bare resources in v1 and none are planned: the payload always hangs off `data`. - A **single resource** is an object in `data`. - A **collection** hangs off a named key inside `data`, together with its `pagination`, also inside `data` — `data: {invoices: [...], pagination: {...}}`. - A collection carries `pagination` unless its operation declares itself a **closed catalogue**: a fixed, bounded list with nothing to page through. The declaration is explicit in the operation; a missing `pagination` is never something to infer. - `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a documented variant of pagination, not another envelope. Putting the array straight into `data` with `pagination` as its sibling is the shape a v2 would adopt; v1 is not being flipped to it.
  - `success` boolean, required
  - `data` object, required: VeriFactu configuration as resolved by the server. **Response only** — the update body is `UpdateVeriFactuConfigurationRequest`, which carries just `enabled`.
    - `enabled` boolean, required: Whether this tax ID is under the VeriFactu regime in the environment of the request: while it is on, every invoice of this tax ID is registered with the AEAT; while it is off, none is. Always `true` in sandbox.
    - `nif_status` 'ACTIVATED' | 'DEACTIVATED', nullable: Whether the tax ID is registered for VeriFactu submission in this environment (null if VeriFactu was never enabled): - ACTIVATED: registered and ready to submit - DEACTIVATED: not registered, or deregistered (data retained for 30 days) **In Live**, `enabled: true` implies `ACTIVATED`: enabling *is* registering, atomically. **In sandbox that implication does not hold**: `enabled` is always `true` there — it is a constant of the mode, not a decision — while the registration travels asynchronously, so `nif_status` can still be `DEACTIVATED` or null for a while. The former `PENDING` and `ERROR` values described an in-between state that no longer exists.
    - `nif_registered_at` string, date-time, nullable: Successful activation date in VeriFactu production
    - `status` 'DISABLED' | 'UNSIGNED' | 'NOT_ACTIVATED' | 'ACTIVE' | 'ERROR', nullable: Single derived VeriFactu state for the (NIF, environment) of the request — the ONE truth every UI surface (settings banner, activation card, invoice form) reads, so they can no longer contradict each other. Read-only; resolved server-side. Null only if never configured. - DISABLED: VeriFactu not enabled for this account. - UNSIGNED: enabled but the NIF has no signed AEAT representation. - NOT_ACTIVATED: signed, but no API key for the operating environment. - ACTIVE: signed and activated with API key: ready to submit. - ERROR: historical, no longer reachable. Activation is atomic: a failed activation leaves nothing persisted and returns the reason, so no row ever rests in this state. Kept in the enum so old clients keep parsing stored values.
    - `signed` boolean, nullable: Secondary flag: whether the NIF has a signed AEAT representation (per-NIF, shared across environments). Data for the wizard, NOT the gate.
    - `activated` boolean, nullable: Secondary flag: whether the NIF is activated (has an API key) in the operating environment (per-environment). Data, NOT the gate.
    - `pdf_generated` boolean, nullable: Secondary flag: whether a representation PDF artifact exists. Used by the signature wizard as secondary data, NOT the source of truth of the gate.
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: Bad request. From a dashboard session, a `BeeL-Environment` header whose value is not a known environment answers `ENVIRONMENT_HEADER_INVALID`, with the header named in `error.details` (`{"BeeL-Environment": "invalid"}`); it is never read as production. `BUSINESS_RULE_VIOLATION` is the generic answer to an operation the resource does not admit in its current state when that state has no code of its own. It carries no `details`; read the resource again before retrying.
- `401`: Missing or invalid authentication. Like every other error, `message`/`detail` is localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the header is missing or asks for none of those.
- `403`: Authenticated but not allowed. Fourteen causes shared by every operation, told apart by `error.code`; an operation that adds a cause of its own names it in its `403`. You can branch on them exhaustively. - `INSUFFICIENT_SCOPE` — the credential lacks a scope the operation requires; `error.details.missing_scopes` names them as a single comma-separated string (for example `"invoices:write,customers:read"`), not as an array; `required_scopes` has the same shape and lists every scope the operation needs. Retrying will not help: mint a key that holds them. - `COMPANY_READ_ONLY` — the scope is there, but your access level over that NIF only lets you read it. - `ACCOUNT_MANAGEMENT_FORBIDDEN` — the scope is there, but your role over the account, or the management relationship you hold over it, does not cover this operation. - `ACCOUNT_NOT_ACCESSIBLE` — the account is not yours to reach, which is also the answer when it does not exist, so existence is never disclosed. - `ACTIVE_COMPANY_NOT_ACCESSIBLE` — the same, for a NIF: the company in the path, or the one named by `BeeL-Active-Company`, is not one this credential may reach — and again, this is also the answer when it does not exist. - `COMPANY_ACCESS_REVOKED` — your access to the NIF was withdrawn while the request was already in flight, so the write was rejected and nothing was recorded. Retrying will not help until the access is granted again. - `LIVE_CREDENTIAL_REQUIRED` — the operation changes the real account and the call came from a test API key (`beel_sk_test_…`). Use your live key or the dashboard. - `FEATURE_NOT_AVAILABLE` — your subscription does not include the entitlement the operation needs; `error.details.feature_code` names it. This gate runs **before** the scope gate, so for such an operation you never see `INSUFFICIENT_SCOPE` first. - `NO_ACTIVE_ACCOUNT` — the credential does not resolve to an account, so no scope can be evaluated against one. Fail-closed, not a permission that can be granted to you. - `OPERATION_REQUIRES_SESSION` — the operation is available only from the web session; no API key and no OAuth2 token can perform it, whatever scopes it holds. - `OPERATION_REQUIRES_API_KEY` — the reverse: the operation needs a scope that only an API key can hold (such as `accounts:write`), so a dashboard session never satisfies it. - `API_KEY_LIVE_NOT_AVAILABLE` — the call came with a live API key (`beel_sk_live_…`) and the account has no Live mode yet. It is never degraded to Test: use your test key, or activate Live first. - `ACCOUNT_REQUIRES_FOCUS_SWITCH` — dashboard session only: you are a member of the account in the path, but it is not the account in focus. Switch focus to it and retry. - `REQUEST_BLOCKED` — the edge security filter stopped the request before the API saw its body, usually because a text field carries something that looks like markup or a script (HTML tags, `<script>`). Review the body and retry.
- `422`: `VALIDATION_ERROR` — the body parses but a value is not acceptable. `details` is a flat map from the offending property's name (in the contract's `snake_case`, nested paths joined with a dot, e.g. `recipient.address.postal_code`) to a message describing what is wrong with it, one entry per property. When the rejected value is one outside an enum's vocabulary, `details` instead follows `FieldDeserializationError` (`field`, `invalid_value`, `allowed_values`). Two more codes share this status for a value that passes the contract but does not fit where it is stored: `VALUE_TOO_LONG` (a text longer than its column holds) and `VALUE_OUT_OF_RANGE` (a number outside what its column holds). Nothing was written.
- `500`: Internal server error
- `default`: Any status code the operation does not list above. Every operation declares it, so a generated client always has a branch to fall into and never loses the cause of a failure it did not anticipate. This is where the transport-level answers land — `405`, `406`, `415` and `429` — together with any status a future version of the API starts returning. All of them carry the same error envelope as the codes listed explicitly, so `error.code` is what tells them apart: switching on the status code alone is not enough. See «Transport-level errors» in the API description for when each one is produced. A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active subscription. `error.details` says why and what to do: `current_status` (absent if it never had one), `required_permission` and `action`. A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a dependency of BeeL itself (the database, the email provider) was momentarily unavailable and nothing was done. Retry after those seconds. A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the operation depends on failed or did not answer in time. It is a transient condition — retry with the same `Idempotency-Key` where the operation accepts one. One exception to the envelope: a failure of the network edge that never reaches the application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not BeeL's — it may not even be JSON. Treat those as "no answer", and retry.

---

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