# Update the VeriFactu configuration of a company API Reference

Replaces the VeriFactu configuration of a company.

- **Writable fields:** only `enabled`, and it is required — this is a full replacement, not
  a partial merge. The rest of the returned configuration is resolved server-side.
- **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 this company's invoices to AEAT and starts
deregistering the NIF from VeriFactu submission. It does not deactivate the
company: the activation is a fact of its own for the (company, environment) pair, so the
company keeps issuing in that environment and stays `ready`. Releasing the NIF — and in
Live freeing it for another account — is always
`DELETE /v1/companies/{company_id}/activations`.


## PUT /v1/companies/{company_id}/verifactu-configuration

**Update the VeriFactu configuration of a company**

Replaces the VeriFactu configuration of a company.

- **Writable fields:** only `enabled`, and it is required — this is a full replacement, not
  a partial merge. The rest of the returned configuration is resolved server-side.
- **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 this company's invoices to AEAT and starts
deregistering the NIF from VeriFactu submission. It does not deactivate the
company: the activation is a fact of its own for the (company, environment) pair, so the
company keeps issuing in that environment and stays `ready`. Releasing the NIF — and in
Live freeing it for another account — is always
`DELETE /v1/companies/{company_id}/activations`.

### Authentication

Accepts any of:

- `ApiKeyAuth` (HTTP bearer, token format `beel_sk_*`)

### Parameters

- **company_id** (required) in path `string`: 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.

### Request Body

Required.

**Content `application/json`:**

- **enabled** (required) `boolean`: 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.

**Example `enable_veri_factu`** — Turn VeriFactu on:

```json
{
  "enabled": true
}
```

**Example `disable_veri_factu`** — Turn VeriFactu off:

```json
{
  "enabled": false
}
```

### Responses

#### 200: Configuration updated successfully

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: true)
- **data** (required): The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
- **meta** `ResponseMeta`
- **data** `VeriFactuConfiguration`: VeriFactu configuration as resolved by the server. **Response only** — the update body is `UpdateVeriFactuConfigurationRequest`, which carries just `enabled`.

#### 400: Turning VeriFactu on registers the NIF with the VeriFactu provider in this same call; when
that registration cannot happen nothing is persisted. Branch on `error.code`:

* `COMPANY_NIF_MISSING` — the company has no NIF to register.
* `API_COMMUNICATION_ERROR` — the provider refused the registration or could not be
  reached. `error.message` carries the provider's reason when it gave one; repeat the
  request once it is fixed or later.

A body that cannot be parsed is also answered with `400`.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

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


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication is required to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 403: Your account does not own or manage this company, or does not hold the required
access over it. A NIF that does not exist answers the same way.
That case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

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


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "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: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is
over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because
too many went out to that recipient in a short time. Either way, retry after the
`Retry-After` seconds.

`QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan
for the current period (issued invoices, for instance). Retrying does not help until the
quota resets or the plan changes. `error.details` carries `feature_code`,
`current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until
the reset when it is known.


**Headers:**

- `Retry-After` `integer`: Seconds until the rate limit resets
- `RateLimit-Limit` `integer`: Maximum requests allowed in the window
- `RateLimit-Remaining` `integer`: Remaining requests in the current window
- `RateLimit-Reset` `integer`: Seconds until the current window resets

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again in 60 seconds."
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 500: Internal server error

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### default: Any status code the operation does not list above. Every operation declares it, so a
generated client always has a branch to fall into and never loses the cause of a failure
it did not anticipate.

This is where the transport-level answers land — `405`, `406`, `415` and `429` — together
with any status a future version of the API starts returning. All of them carry the same
error envelope as the codes listed explicitly, so `error.code` is what tells them apart:
switching on the status code alone is not enough. See «Transport-level errors» in the
API description for when each one is produced.

A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active
subscription. `error.details` says why and what to do: `current_status` (absent if it
never had one), `required_permission` and `action`.

A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a
dependency of BeeL itself (the database, the email provider) was momentarily
unavailable and nothing was done. Retry after those seconds.

A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the
operation depends on failed or did not answer in time. It is a transient condition — retry
with the same `Idempotency-Key` where the operation accepts one.

One exception to the envelope: a failure of the network edge that never reaches the
application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not
BeeL's — it may not even be JSON. Treat those as "no answer", and retry.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_MEDIA_TYPE",
    "message": "Unsupported media type: text/plain. Supported: application/json"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

---

# Related Schema Definitions

## 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** (required) `boolean`: 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.

## SuccessResponse

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

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

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

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

## ResponseMeta

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

## VeriFactuConfiguration

VeriFactu configuration as resolved by the server. **Response only** — the update body is
`UpdateVeriFactuConfigurationRequest`, which carries just `enabled`.

- **enabled** (required) `boolean`: 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** `string`: 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. — one of: ACTIVATED, DEACTIVATED
- **nif_registered_at** `string` (date-time): Successful activation date in VeriFactu production
- **status** `string`: 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. — one of: DISABLED, UNSIGNED, NOT_ACTIVATED, ACTIVE, ERROR
- **signed** `boolean`: Secondary flag: whether the NIF has a signed AEAT representation (per-NIF, shared across environments). Data for the wizard, NOT the gate.
- **activated** `boolean`: Secondary flag: whether the NIF is activated (has an API key) in the operating environment (per-environment). Data, NOT the gate.
- **pdf_generated** `boolean`: Secondary flag: whether a representation PDF artifact exists. Used by the signature wizard as secondary data, NOT the source of truth of the gate.

## ErrorResponse

Error response shared by all BeeL. APIs.

The payload carries **two contracts at once** (additive, non-breaking):

- **Legacy** (`success`, `error.{code,message,details}`, `meta`) — kept
  intact for existing consumers.
- **RFC 9457** (`type`, `title`, `detail`, `instance`) — new fields
  for integrators following Problem Details for HTTP APIs. The
  `type` URI is the stable, shareable link to the error's
  documentation page (e.g. `https://docs.beel.es/errors/{code}`).

Future migration: the legacy fields will be deprecated via
`Deprecation`/`Sunset` headers after a sufficient adoption window,
and the response Content-Type will move to
`application/problem+json`.

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

## ErrorDetail

- **code** (required) `string`: No description (example: "VALIDATION_ERROR")
- **message** (required) `string`: No description (example: "The provided data is not valid")
- **details** `object`: No description (example: {"field":"specific error message"})


---

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