# Get a company API Reference

Returns the identity and activation state of a company: its fiscal data, whether it
is switched on in Test and in Live, and its VeriFactu registration state.

It also returns **every field `PATCH /v1/companies/{company_id}` accepts** — contact
details, legal representative, bank details, IAE, activity start date, payment term and
the rendering block — so what was written can be read back without keeping a copy of it.
A field never set comes back absent: that means "nothing stored", not "hidden".

Its invoice series are not part of this response: read them from
`GET /v1/companies/{company_id}/series`.


## GET /v1/companies/{company_id}

**Get a company**

Returns the identity and activation state of a company: its fiscal data, whether it
is switched on in Test and in Live, and its VeriFactu registration state.

It also returns **every field `PATCH /v1/companies/{company_id}` accepts** — contact
details, legal representative, bank details, IAE, activity start date, payment term and
the rendering block — so what was written can be read back without keeping a copy of it.
A field never set comes back absent: that means "nothing stored", not "hidden".

Its invoice series are not part of this response: read them from
`GET /v1/companies/{company_id}/series`.

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

### Responses

#### 200: Company details

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

#### 400: The request target could not be read as declared, so nothing was looked up. Three causes,
all named in `error.details`: a path or query parameter whose value does not parse as its
type (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty
element in a list such as `status=ISSUED,`), a required parameter that was not sent
(`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not
(`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is
not an error: it is the same as omitting it.

The fault is in the target, not in the content — which is what `422` is defined over.


**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 parameter 'invoice_id' has an invalid type. Expected: UUID.",
    "details": {
      "field": "invoice_id",
      "invalid_value": "deliveries",
      "expected_format": "UUID"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 401: 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.
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")

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

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