# Update a company API Reference

Updates the editable fields of a company; the set is the one
`UpdateCompanyRequest` declares.

- **Immutable fields:** `nif`, `entity_type` and `legal_form`, once set. Sending one of
  them with a different value answers `422` with a code that names the field; sending the
  value it already has is not a change.
- **`legal_name`:** changing it requires the NIF to pass an AEAT census re-validation. For a
  legal entity (`LEGAL_ENTITY`) the census identifies the company by its NIF alone: the name
  is not verified, so the name sent cannot make it fail. For an `INDIVIDUAL` the name must
  match the one the census holds for that NIF.
- **Census not answering:** if the AEAT census cannot be reached, the change is not
  rejected. The response is `200` with the new `legal_name` stored, and BeeL repeats the
  census check in the background. The outcome of that check is not part of this resource:
  no field of `CompanyData` carries it. To know what the census says about a NIF and a name,
  ask `POST /v1/nif/validate`.
- **Addresses:** a Spanish postal code (`country_code` omitted or `ES`) must have 5 digits,
  in `address` and in `legal_representative.address`; otherwise `422 POSTAL_CODE_INVALID_ES`.
  Other countries' postal codes are free-form.

## Test credentials on a Live company

Once the company is activated in Live, a test credential may only write the fields that
affect how the invoice looks: `logo_url`, `invoice_accent_color`,
`invoice_template_type`, `invoice_language`, `email_language` and `additional_info`. Any
other field describes the real business — fiscal address, legal representative, bank
details, contact data, IAE, activity start date, payment term — and answers
`422 FISCAL_IDENTITY_LIVE_ONLY` from Test, since the company is a single record shared by
both modes. A company not activated in Live accepts the whole body from Test, and sending
a field its current value is never a change.

## What comes back

The `200` returns `CompanyData` with **every field this request accepts**, under the same
name and the same type — so the response is the confirmation of what was stored, and a
later `GET` says the same. A field you never set comes back absent, which means "nothing
stored", not "hidden".

Two things live outside this body and keep their own reads: the invoice series
(`GET /v1/companies/{company_id}/series`) and the rendering block, which is also served
on its own by `GET /v1/companies/{company_id}/invoice-customization`.


## PATCH /v1/companies/{company_id}

**Update a company**

Updates the editable fields of a company; the set is the one
`UpdateCompanyRequest` declares.

- **Immutable fields:** `nif`, `entity_type` and `legal_form`, once set. Sending one of
  them with a different value answers `422` with a code that names the field; sending the
  value it already has is not a change.
- **`legal_name`:** changing it requires the NIF to pass an AEAT census re-validation. For a
  legal entity (`LEGAL_ENTITY`) the census identifies the company by its NIF alone: the name
  is not verified, so the name sent cannot make it fail. For an `INDIVIDUAL` the name must
  match the one the census holds for that NIF.
- **Census not answering:** if the AEAT census cannot be reached, the change is not
  rejected. The response is `200` with the new `legal_name` stored, and BeeL repeats the
  census check in the background. The outcome of that check is not part of this resource:
  no field of `CompanyData` carries it. To know what the census says about a NIF and a name,
  ask `POST /v1/nif/validate`.
- **Addresses:** a Spanish postal code (`country_code` omitted or `ES`) must have 5 digits,
  in `address` and in `legal_representative.address`; otherwise `422 POSTAL_CODE_INVALID_ES`.
  Other countries' postal codes are free-form.

## Test credentials on a Live company

Once the company is activated in Live, a test credential may only write the fields that
affect how the invoice looks: `logo_url`, `invoice_accent_color`,
`invoice_template_type`, `invoice_language`, `email_language` and `additional_info`. Any
other field describes the real business — fiscal address, legal representative, bank
details, contact data, IAE, activity start date, payment term — and answers
`422 FISCAL_IDENTITY_LIVE_ONLY` from Test, since the company is a single record shared by
both modes. A company not activated in Live accepts the whole body from Test, and sending
a field its current value is never a change.

## What comes back

The `200` returns `CompanyData` with **every field this request accepts**, under the same
name and the same type — so the response is the confirmation of what was stored, and a
later `GET` says the same. A field you never set comes back absent, which means "nothing
stored", not "hidden".

Two things live outside this body and keep their own reads: the invoice series
(`GET /v1/companies/{company_id}/series`) and the rendering block, which is also served
on its own by `GET /v1/companies/{company_id}/invoice-customization`.

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

- **entity_type**: No description
- **legal_name** `string`: Legal/fiscal name. Changing it triggers AEAT census re-validation of the NIF. For a self-employed individual the census matches NIF and name together, so a name it does not recognise is rejected. For a legal entity the name is **not verified**: the re-validation only confirms the CIF, and the business name held by the census is the only thing to contrast yours against.
- **nif**: No description
- **legal_form** `string`: Legal form (SL, SA, ...). IMMUTABLE once set. Only for LEGAL_ENTITY.
- **trade_name** `string`: Commercial/trade name for the company
- **address** `Address`: Address you send when you create or update a company, a customer or an onboarding. A required field made only of whitespace passes the length checks below but leaves the address without that part, so it is rejected with `422`: `STREET_EMPTY`, `POSTAL_CODE_EMPTY`, `CITY_EMPTY` or `PROVINCE_EMPTY`. Addresses you read back are described by their own schema.
- **legal_representative** `LegalRepresentative`: Legal representative data for a legal entity. Only used when entity_type = LEGAL_ENTITY.
- **phone**: No description
- **email**: No description
- **website** `string`: Website
- **logo_url** `string`: Logo URL
- **additional_info** `string`: Free note printed on the invoice. It is presentation, not fiscal identity, so a test credential may change it even on a company activated in Live. Read it back in `CompanyData.additional_info` (`GET /v1/companies/{company_id}`).
- **default_iban**: No description
- **default_swift**: No description
- **account_holder** `string`: Bank account holder
- **iae** `string`: IAE code
- **activity_start_date** `string` (date): Activity start date
- **default_payment_term** `integer`: Default payment term in days
- **invoice_template_type**: Invoice PDF template type
- **invoice_accent_color** `string`: Invoice PDF accent color (#RRGGBB) (example: "#fc481d")
- **invoice_language**: No description
- **email_language**: No description

**Example `patch_company_contact_and_bank`** — Update contact and bank details:

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

### Responses

#### 200: Company updated. The body is `CompanyData`, which echoes every field this request
accepts — the stored value, not the one you sent, so it is also the confirmation of
what a partial update left untouched.


**Content `application/json`:**

- **success** `boolean`: No description
- **data** `CompanyData`: A company owned by the account. Its status is described by **two independent axes** — do not conflate them: * **Mode activation** (`in_test` / `in_prod`) — which modes this NIF is activated in, matching the dashboard's Test/Live switch. A company is a single env-agnostic record — there is no Test copy and no Live copy of it; what is per-mode is the *activation*, and a NIF can be activated in Test, in Live, in both, or in neither. It decides where invoices and quota are accounted, NOT whether they reach AEAT. * **AEAT emission capability** (`account_state`, `verifactu_status`, and the `readiness` block requested with `?include=readiness`) — whether invoices are submitted to AEAT with a signed VeriFactu representation. A company can bill in the **Live** mode and still have AEAT emission disabled (e.g. before the VeriFactu representation is signed). `TEST_ONLY` / `in_prod=false` therefore do **not** mean "this account cannot invoice". **Every field `PATCH /v1/companies/{company_id}` accepts is read back here** — same names, same types as `UpdateCompanyRequest`. A field with no value is absent, which means "nothing stored", never "hidden from you": the field set does not depend on who asks (owner, member with `VIEW`, API key or the managed twin `GET /v1/accounts/{account_id}/companies` all see the same one).
- **meta** `ResponseMeta`

#### 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 with write access.
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 or business rule error. These codes are specific to this operation:

- `FISCAL_IDENTITY_LIVE_ONLY` — the call came from Test on a company that is
  activated in Live and touched a field outside the six a test credential may
  write (`logo_url`, `invoice_accent_color`, `invoice_template_type`,
  `invoice_language`, `email_language`, `additional_info`). Repeat it with a live
  key; nothing was written.
- `IMMUTABLE_NIF`, `IMMUTABLE_ENTITY_TYPE`, `IMMUTABLE_LEGAL_FORM` — the request
  changes `nif`, `entity_type` or `legal_form`, which cannot change once set. Nothing
  was written; drop the field from the request.
- `NIF_CENSUS_MISMATCH`, `NIF_DE_BAJA`, `NIF_REVOCADO` — the request changes
  `legal_name` and the AEAT census re-validation rejects it: the census does not
  recognise the NIF (for an `INDIVIDUAL`, with that name; for a legal entity the name is
  not verified, only the NIF), or the NIF is deregistered or revoked. Nothing was
  written.
- `POSTAL_CODE_INVALID_ES` — a Spanish postal code in `address` or in
  `legal_representative.address` does not have 5 digits. Nothing was written.
- `INVALID_IBAN` — `default_iban` is not an IBAN: two letters, two digits and up to 30
  letters or digits, 15 to 34 characters in all, once it is upper-cased and anything
  that is not a letter or a digit is removed. Nothing was written.
- `SWIFT_INVALID` — `default_swift` is not a SWIFT/BIC of 8 or 11 characters.
  Nothing was written.
- `PHYSICAL_PERSON_CANNOT_HAVE_LEGAL_FORM`, `PHYSICAL_PERSON_CANNOT_HAVE_REPRESENTATIVE`
  — the company, as it would stand after the change, is an `INDIVIDUAL` with a
  `legal_form` or a `legal_representative`; both only exist for a `LEGAL_ENTITY`.
  Nothing was written.
- `UNPROCESSABLE_ENTITY` — the generic validation failure, with the offending
  field in `error.details`.


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

## UpdateCompanyRequest

Editable fields of a company.
`entity_type`, `nif` and `legal_form` are immutable once set;
`legal_name` can only be corrected together with a successful
AEAT census re-validation of the NIF. For a company that
re-validation checks the CIF only — the name is **not verified**,
so it can never fail because of the name you send.

**Everything here reads back.** The `200` of the update — and every later
`GET /v1/companies/{company_id}` — returns `CompanyData`, which carries each of these
fields under the same name and the same type. No need to keep your own copy to
reconcile: write it, read it back. Five of them also have a read of their own,
`logo_url`, `invoice_template_type`, `invoice_accent_color`, `invoice_language` and
`email_language`, all returned by
`GET /v1/companies/{company_id}/invoice-customization`.

A field you never set is **absent** from the read, which means "nothing stored" — not
"hidden from you", and not a default. What varies by caller is what you may *write*
(see the Test-credential rule below), never what comes back.

**From Test, on a company activated in Live, only six of these fields are writable**:
`logo_url`, `invoice_accent_color`, `invoice_template_type`, `invoice_language`,
`email_language` and `additional_info` — what the invoice *looks like* and the free
note it carries. Everything else describes the real business and answers
`422 FISCAL_IDENTITY_LIVE_ONLY` unless the call is made with a live credential.

- **entity_type**: No description
- **legal_name** `string`: Legal/fiscal name. Changing it triggers AEAT census re-validation of the NIF. For a self-employed individual the census matches NIF and name together, so a name it does not recognise is rejected. For a legal entity the name is **not verified**: the re-validation only confirms the CIF, and the business name held by the census is the only thing to contrast yours against.
- **nif**: No description
- **legal_form** `string`: Legal form (SL, SA, ...). IMMUTABLE once set. Only for LEGAL_ENTITY.
- **trade_name** `string`: Commercial/trade name for the company
- **address** `Address`: Address you send when you create or update a company, a customer or an onboarding. A required field made only of whitespace passes the length checks below but leaves the address without that part, so it is rejected with `422`: `STREET_EMPTY`, `POSTAL_CODE_EMPTY`, `CITY_EMPTY` or `PROVINCE_EMPTY`. Addresses you read back are described by their own schema.
- **legal_representative** `LegalRepresentative`: Legal representative data for a legal entity. Only used when entity_type = LEGAL_ENTITY.
- **phone**: No description
- **email**: No description
- **website** `string`: Website
- **logo_url** `string`: Logo URL
- **additional_info** `string`: Free note printed on the invoice. It is presentation, not fiscal identity, so a test credential may change it even on a company activated in Live. Read it back in `CompanyData.additional_info` (`GET /v1/companies/{company_id}`).
- **default_iban**: No description
- **default_swift**: No description
- **account_holder** `string`: Bank account holder
- **iae** `string`: IAE code
- **activity_start_date** `string` (date): Activity start date
- **default_payment_term** `integer`: Default payment term in days
- **invoice_template_type**: Invoice PDF template type
- **invoice_accent_color** `string`: Invoice PDF accent color (#RRGGBB) (example: "#fc481d")
- **invoice_language**: No description
- **email_language**: No description

## Address

Address you send when you create or update a company, a customer or an onboarding.

A required field made only of whitespace passes the length checks below but leaves the
address without that part, so it is rejected with `422`: `STREET_EMPTY`,
`POSTAL_CODE_EMPTY`, `CITY_EMPTY` or `PROVINCE_EMPTY`.

Addresses you read back are described by their own schema.

- **street** (required) `string`: Full address (street, number, floor, etc.) - Latin characters only (example: "Calle Mayor, 123")
- **number** `string`: Street number. Optional: omit it when the address has none, or when `street` already carries the address in full. (example: "123")
- **floor** `string`: Floor or level (example: "2º A")
- **door** `string`: Door or apartment (example: "A")
- **postal_code** (required) `string`: Postal code (5 digits for Spain, free format for other countries) (example: "28001")
- **city** (required) `string`: City or town - Latin characters only (example: "Madrid")
- **province** `string`: Province or state - Latin characters only. Required for an address in Spain (`country_code` `ES`, or no country at all); optional elsewhere, where many addresses have none. A Spanish address without it is rejected with a `422`. (example: "Madrid")
- **country** `string`: Country of the address, as its ISO 3166-1 alpha-2 code (`GB`) or its official name in Spanish, English or Catalan (`Reino Unido`, `United Kingdom`, `Regne Unit`; case and accents are ignored). Anything else, such as `UK`, is rejected with `422 COUNTRY_CODE_REQUIRED`: send `country_code` instead. If it names a different country than `country_code`, `422 COUNTRY_CODE_MISMATCH` (`España` alone yields to a foreign `country_code`: it was the old default). What is stored and returned is always the Spanish name derived from the resulting code, never the text sent. With neither field present, the address is Spanish (`España`). (example: "España")
- **country_code** `string`: ISO 3166-1 alpha-2 country code: the canonical field that decides the country of the address. It must be a real country code (`GB`, not `UK`); otherwise `422 COUNTRY_CODE_REQUIRED`. When it is omitted, the code comes from `country` (see there). With neither field present, the address is stored as `ES`. (example: "ES")

## LegalRepresentative

Legal representative data for a legal entity.
Only used when entity_type = LEGAL_ENTITY.

- **full_name** (required) `string`: Full name of the legal representative (example: "María García López")
- **nif** (required) `string`: Tax ID of the legal representative (DNI/CIF/NIE) (example: "12345678A")
- **address** (required): No description

## CompanyResponse

- **success** `boolean`: No description
- **data** `CompanyData`: A company owned by the account. Its status is described by **two independent axes** — do not conflate them: * **Mode activation** (`in_test` / `in_prod`) — which modes this NIF is activated in, matching the dashboard's Test/Live switch. A company is a single env-agnostic record — there is no Test copy and no Live copy of it; what is per-mode is the *activation*, and a NIF can be activated in Test, in Live, in both, or in neither. It decides where invoices and quota are accounted, NOT whether they reach AEAT. * **AEAT emission capability** (`account_state`, `verifactu_status`, and the `readiness` block requested with `?include=readiness`) — whether invoices are submitted to AEAT with a signed VeriFactu representation. A company can bill in the **Live** mode and still have AEAT emission disabled (e.g. before the VeriFactu representation is signed). `TEST_ONLY` / `in_prod=false` therefore do **not** mean "this account cannot invoice". **Every field `PATCH /v1/companies/{company_id}` accepts is read back here** — same names, same types as `UpdateCompanyRequest`. A field with no value is absent, which means "nothing stored", never "hidden from you": the field set does not depend on who asks (owner, member with `VIEW`, API key or the managed twin `GET /v1/accounts/{account_id}/companies` all see the same one).
- **meta** `ResponseMeta`

## CompanyData

A company owned by the account.

Its status is described by **two independent axes** — do not conflate them:

* **Mode activation** (`in_test` / `in_prod`) — which modes this NIF is
  activated in, matching the dashboard's Test/Live switch. A company is a
  single env-agnostic record — there is no Test copy and no Live copy of it;
  what is per-mode is the *activation*, and a NIF can be activated in Test, in
  Live, in both, or in neither. It decides where invoices and quota are
  accounted, NOT whether they reach AEAT.
* **AEAT emission capability** (`account_state`, `verifactu_status`, and the
  `readiness` block requested with `?include=readiness`) — whether invoices are
  submitted to AEAT with a signed VeriFactu representation.

A company can bill in the **Live** mode and still have AEAT emission disabled
(e.g. before the VeriFactu representation is signed). `TEST_ONLY` /
`in_prod=false` therefore do **not** mean "this account cannot invoice".

**Every field `PATCH /v1/companies/{company_id}` accepts is read back here** — same
names, same types as `UpdateCompanyRequest`. A field with no value is absent, which
means "nothing stored", never "hidden from you": the field set does not depend on who
asks (owner, member with `VIEW`, API key or the managed twin
`GET /v1/accounts/{account_id}/companies` all see the same one).

- **id** `string` (uuid): Unique company identifier.
- **access_level**: 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. **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** `string`: Spanish Tax ID (NIF/CIF). Immutable once set.
- **legal_name** `string`: Legal/fiscal name of the company. Immutable once set. It is the name that was sent when the NIF was registered, not one AEAT confirmed: for a legal entity the census identifies the CIF and the name is **not verified**. Validate a NIF (`POST /v1/nif/validate`) to read the business name the census actually holds.
- **trade_name** `string`: 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**: Self-employed individual or legal entity. Immutable once set.
- **is_primary** `boolean`: True for the account's primary company (the owner's own NIF).
- **verifactu_status** `string`: State of the AEAT representation of this NIF, which Live submission to VeriFactu requires. Part of the AEAT-emission axis — independent of `environment`. - `NOT_CONFIGURED`: no representation document, or the last one was cancelled. - `PENDING`: the document is generated and waits to be signed and submitted. - `ACTIVE`: the signed representation is in force. - `ERROR`: reserved; not currently returned. — one of: NOT_CONFIGURED, PENDING, ACTIVE, ERROR
- **environment**: **Deprecated** — use `in_test` / `in_prod` instead. A company is a single env-agnostic record; what is per-mode is its *activation*, and a company can be activated in Test, in Live, in both, or in neither. This single scalar cannot express that: it collapses the pair to one value, so it misreports any company activated in both modes. It is kept for backwards compatibility and will be removed in a future version. **How the collapse is decided** — the same rule wherever this field appears (the single company, the account's list of companies, any of their aliases): the environment of the credential you are calling with, *if* the company is activated there; otherwise the environment it is really activated in. A company activated only in Test therefore reads `TEST` even to a live key: the scalar is never re-labelled to the environment you are looking from. This is the data/billing axis, NOT a measure of AEAT capability.
- **in_prod** `boolean`: Whether this company is **activated in Live** (production), regardless of whether the AEAT representation is already signed. Derived from the real activation fact — NOT from the stamped `environment` scalar — so it stays true even for a NIF registered in production but not yet signed. Drives the UI's "already in production" state (no spurious "add NIF" prompt when the account switches to Live).
- **in_test** `boolean`: Whether this company is **activated in Test** (sandbox). Symmetric to `in_prod` and derived from the same activation fact: activation is independent per mode, so a company may be activated in Test only, in Live only, in both, or in neither. A company that is not activated in the mode you are working in is shown *switched off* in the dashboard (visible and labelled, but not selectable) rather than hidden.
- **aeat_environment**: **Deprecated** — use the activation pair `in_test` / `in_prod`, together with `verifactu_status`, instead. AEAT/VeriFactu environment this NIF is registered against. It is derived, never stored: a single scalar cannot express a NIF activated in both modes, nor one activated in neither. It is `null` whenever no registration can be asserted — no mode activated, or `verifactu_status: NOT_CONFIGURED` — and it is kept for backwards compatibility only.
- **account_state**: Account-level lifecycle, identical for every company of the account. AEAT emission additionally requires the per-NIF representation to be signed.
- **address** `object`: Fiscal address of the company, as set via `POST /v1/accounts/{account_id}/companies` and `PATCH /v1/accounts/{account_id}/companies/{company_id}`. All fields are optional — a company may have a partial address — and the object is `null` when no address has been set.
  - **street** `string`: Street of the fiscal address.
  - **number** `string`: Street number.
  - **floor** `string`: Floor or level.
  - **door** `string`: Door or apartment.
  - **postal_code** `string`: Postal code.
  - **city** `string`: City or town.
  - **province** `string`: Province.
  - **country** `string`: Country name.
  - **country_code** `string`: ISO 3166-1 alpha-2 country code.
- **legal_form** `string`: Legal form (SL, SA, ...). Only for `entity_type: LEGAL_ENTITY`, immutable once set. The field `UpdateCompanyRequest.legal_form` writes.
- **legal_representative**: Legal representative of the company, as stored — the field `UpdateCompanyRequest.legal_representative` writes. Only for `entity_type: LEGAL_ENTITY`; absent when none is recorded.
- **phone**: Contact phone. The field `UpdateCompanyRequest.phone` writes.
- **email**: Contact email. The field `UpdateCompanyRequest.email` writes.
- **website** `string`: Website. The field `UpdateCompanyRequest.website` writes.
- **logo_url** `string`: Public URL of the logo printed on the invoice. The field `UpdateCompanyRequest.logo_url` writes. Also returned by `GET /v1/companies/{company_id}/invoice-customization` and as `issuer.logo_url` of an invoice, always as the same absolute, fetchable URL.
- **additional_info** `string`: Free note printed on the invoice — the field `UpdateCompanyRequest.additional_info` writes.
- **default_iban**: Default IBAN shown on the invoice. The field `UpdateCompanyRequest.default_iban` writes.
- **default_swift**: Default SWIFT/BIC. The field `UpdateCompanyRequest.default_swift` writes.
- **account_holder** `string`: Bank account holder. The field `UpdateCompanyRequest.account_holder` writes.
- **iae** `string`: IAE code. The field `UpdateCompanyRequest.iae` writes.
- **activity_start_date** `string` (date): Activity start date. The field `UpdateCompanyRequest.activity_start_date` writes.
- **default_payment_term** `integer`: Default payment term in days. The field `UpdateCompanyRequest.default_payment_term` writes.
- **invoice_template_type**: Invoice PDF template type. The field `UpdateCompanyRequest.invoice_template_type` writes. Also returned by `GET /v1/companies/{company_id}/invoice-customization`.
- **invoice_accent_color** `string`: Invoice PDF accent color (`#RRGGBB`). The field `UpdateCompanyRequest.invoice_accent_color` writes. Also returned by `GET /v1/companies/{company_id}/invoice-customization`. (example: "#fc481d")
- **invoice_language**: Language used to render invoice PDFs. The field `UpdateCompanyRequest.invoice_language` writes. Also returned by `GET /v1/companies/{company_id}/invoice-customization`.
- **email_language**: Language used for emails. The field `UpdateCompanyRequest.email_language` writes. Also returned by `GET /v1/companies/{company_id}/invoice-customization`.
- **default_irpf_rate** `number`: Default IRPF withholding rate applied to this company's invoices — the same number `POST /v1/accounts/{account_id}/companies` accepts and `GET`/`PUT /v1/companies/{company_id}/tax-configuration` manages, read back where you wrote it. **Absent means no rate was ever declared**, which is not the same as `0`. A company created without `default_irpf_rate` withholds nothing *because nobody said what to withhold*; a company with `0` withholds nothing *because its owner declared so*. Both invoice the same, only one is a declaration — so do not fill the absent case in with a zero. Exemption is a declaration too and reads as `0`. Read-only here. Change it with `PUT /v1/companies/{company_id}/tax-configuration`; this resource does not accept it on `PATCH`. Absent from the `201` of `POST /v1/accounts/{account_id}/companies` (that response describes the company just created, not its resolved tax configuration) — read it back with `GET`.
- **created_at** `string` (date-time): When the company was created (ISO 8601).
- **readiness**: Issuing-readiness status of this company. Present ONLY when the request asks for it via `?include=readiness`; otherwise omitted/null. Same shape as `GET /v1/companies/{company_id}/issuing-readiness`.

## ResponseMeta

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

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