# Switch a company on API Reference

Switches an existing company on in the mode carried in the body. The mode is always
explicit and never taken from the credential's environment, so a Test key can switch a NIF
on in Live.

## Modes and billing

- **`TEST`:** immediate and free.
- **`PROD`:** immediate when the account already has a card on file or an enterprise
  contract, and the NIF is added to the existing subscription. With no card on file it
  answers `402 CHECKOUT_REQUIRED`, returning a `checkout_url` when `success_url` and
  `cancel_url` are supplied. It also requires being the billing subject of the account
  (`403 NOT_BILLING_OWNER` otherwise).
- **`billing_period`:** picks the plan the checkout sells — `MONTHLY` (default) or
  `ANNUAL`. It only matters when a checkout is opened. Accounts with a negotiated rate or
  an enterprise contract cannot buy the annual plan (`422 ANNUAL_PLAN_NOT_AVAILABLE`).

## Idempotency and pending switch-offs

- **Repeating the call:** opens no second checkout and adds no second subscription item; it
  returns the existing activation with `already_active: true`. The same `Idempotency-Key`
  sent to this route and to the nested one it replaces is the same operation, so it is
  replayed and never charged twice.
- **A pending switch-off is cancelled:** while it is pending the NIF is still on — it just
  carries an effective date — so switching it on again only removes that date, answers
  `scheduled_deactivation_cancelled: true`, and charges or credits nothing. It does commit
  the NIF to the next billing cycle, though, so under an agreement capped at a number of
  Live NIFs it asks for a seat like a new NIF would, and is rejected with
  `409 NIF_LIMIT_REACHED` when the cap is already full.


## POST /v1/companies/{company_id}/activations

**Switch a company on**

Switches an existing company on in the mode carried in the body. The mode is always
explicit and never taken from the credential's environment, so a Test key can switch a NIF
on in Live.

## Modes and billing

- **`TEST`:** immediate and free.
- **`PROD`:** immediate when the account already has a card on file or an enterprise
  contract, and the NIF is added to the existing subscription. With no card on file it
  answers `402 CHECKOUT_REQUIRED`, returning a `checkout_url` when `success_url` and
  `cancel_url` are supplied. It also requires being the billing subject of the account
  (`403 NOT_BILLING_OWNER` otherwise).
- **`billing_period`:** picks the plan the checkout sells — `MONTHLY` (default) or
  `ANNUAL`. It only matters when a checkout is opened. Accounts with a negotiated rate or
  an enterprise contract cannot buy the annual plan (`422 ANNUAL_PLAN_NOT_AVAILABLE`).

## Idempotency and pending switch-offs

- **Repeating the call:** opens no second checkout and adds no second subscription item; it
  returns the existing activation with `already_active: true`. The same `Idempotency-Key`
  sent to this route and to the nested one it replaces is the same operation, so it is
  replayed and never charged twice.
- **A pending switch-off is cancelled:** while it is pending the NIF is still on — it just
  carries an effective date — so switching it on again only removes that date, answers
  `scheduled_deactivation_cancelled: true`, and charges or credits nothing. It does commit
  the NIF to the next billing cycle, though, so under an agreement capped at a number of
  Live NIFs it asks for a seat like a new NIF would, and is rejected with
  `409 NIF_LIMIT_REACHED` when the cap is already full.

### Authentication

Accepts any of:

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

### Parameters

- **company_id** (required) in path `string`: 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.
- **Idempotency-Key** (optional) in header `string`: Idempotency key to prevent duplicates in sensitive operations. - Any unique client-generated string (e.g. an order id). A UUID also works but is not required - Allowed characters: letters, digits, `_` and `-` (max 255 chars) - Retrying with the same key replays the first response when it was a success (2xx) or a server error (5xx): same status and body, plus the header `Idempotency-Replay: true`. After a 5xx, check whether the operation took effect before retrying with a **new** key - A 4xx is not stored: the key is released, so the corrected request can reuse it - Stored responses expire 24 hours after processing The key is scoped per user and environment, and bound to the request body, so retrying after a network timeout replays the stored response instead of repeating the operation. | Status | Code | When | |---|---|---| | `400` | `INVALID_IDEMPOTENCY_KEY` | The key breaks the format rules above. | | `409` | `IDEMPOTENCY_KEY_PROCESSING` | The first request is still in flight. Wait for the `Retry-After` seconds (2) and retry with the same key. | | `409` | `IDEMPOTENCY_KEY_MISMATCH` | The key was already used with a **different** body. Use a new key. |

### Request Body

Required.

**Content `application/json`:**

- **environment** (required) `Environment`: Mode a record lives in — its Test/Live twin. It decides where invoices, customers and quota are accounted. For a company it also decides which AEAT its NIF is registered against: switching a company on in `PROD` is what registers it with the real AEAT, so `aeat_environment` is that same mode and uses this same enum.
- **success_url** `string`: 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** `string` (uri): Where Stripe returns if the checkout is abandoned. (example: "https://your-app.example.com/billing")
- **billing_period**: 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.

**Example:**

```json
{
  "environment": "PROD"
}
```

### Responses

#### 201: The NIF is switched on in that mode.

**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** `CompanyActivation`

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


**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": "BAD_REQUEST",
    "message": "Invalid request"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 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"
  }
}
```

#### 402: 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`:**

- **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")

#### 403: `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`:**

- **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 `contact_sales_live_with_multiple_nifs`** — API plan without an agreed price goes Live with several NIFs:

```json
{
  "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: The NIF cannot be switched on in Live. Branch on `error.code`:

* `NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT` — another account already has this NIF in
  Live. A NIF can only invoice in Live from one account, so the other one has to switch
  it off first.
* `NIF_LIMIT_REACHED` — the account's agreement caps how many NIFs can be active in Live,
  and the cap is full. It applies to a NIF that does not hold a seat yet: a new one, one
  whose switch-off already took effect, or one whose pending switch-off this call would
  cancel. `error.details` is empty; the cap travels in `error.message`. Switch another
  NIF off in Live, or contact BeeL to raise the cap. Test is never capped.

Nothing is switched on in either case.


**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 `nif_prod_already_active_in_another_account`** — The NIF is live in another account:

```json
{
  "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"
  }
}
```

**Example `nif_limit_reached`** — The agreement's cap of Live NIFs is full:

```json
{
  "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: `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`:**

- **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")

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

## ActivateCompanyRequest

- **environment** (required) `Environment`: Mode a record lives in — its Test/Live twin. It decides where invoices, customers and quota are accounted. For a company it also decides which AEAT its NIF is registered against: switching a company on in `PROD` is what registers it with the real AEAT, so `aeat_environment` is that same mode and uses this same enum.
- **success_url** `string`: 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** `string` (uri): Where Stripe returns if the checkout is abandoned. (example: "https://your-app.example.com/billing")
- **billing_period**: 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.

## Environment

Mode a record lives in — its Test/Live twin. It decides where invoices, customers and
quota are accounted.

For a company it also decides which AEAT its NIF is registered against: switching a
company on in `PROD` is what registers it with the real AEAT, so `aeat_environment`
is that same mode and uses this same enum.

Type: `string` — one of: TEST, PROD

## CompanyActivationResponse

- **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** `CompanyActivation`

## 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")

## CompanyActivation

- **company_id** (required) `string` (uuid): No description
- **environment** (required) `Environment`: Mode a record lives in — its Test/Live twin. It decides where invoices, customers and quota are accounted. For a company it also decides which AEAT its NIF is registered against: switching a company on in `PROD` is what registers it with the real AEAT, so `aeat_environment` is that same mode and uses this same enum.
- **already_active** (required) `boolean`: The company was already on in that mode and nothing was written.
- **scheduled_deactivation_cancelled** (required) `boolean`: The call removed a pending switch-off. Comes with `already_active: true`, since the activation never stopped being live. Nothing was charged or credited.

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