# Companies & fiscal profile

Create, list, read, update and delete the companies (NIFs) under your account, including the TEST vs PROD creation flow.

A **company** is one NIF/CIF under your account, with its own fiscal profile,
invoices and series. This page covers the full lifecycle — the order of the calls, what
each one commits you to, and where it can fail. The exact fields, types and schemas of
every operation live in the [Companies API Reference](/companies/listCompanies).

<Callout type="info">
  **Creating and listing NIFs hangs off the account** —
  `/v1/accounts/{account_id}/companies` — because that is where a NIF is born; the
  `{account_id}` is yours or one you [manage](/multi-nif/managed-accounts). Everything you
  then do *to* one NIF is addressed by the NIF itself: `/v1/companies/{company_id}/…`. See
  [Where each identifier goes](/multi-nif#where-each-identifier-goes).
</Callout>

## Create a company

`POST /v1/accounts/{account_id}/companies` — requires the **`companies:write`**
scope. By default the NIF is also **switched on** in the requested mode, which is
what seeds its invoice series (ordinary `F`, simplified `S`, corrective `R`) and its
tax defaults, so the company can issue right away — rename or edit them later in the
app. Send `activate: false` to create only the NIF profile.

```bash
curl -X POST https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/companies \
  -H "Authorization: Bearer beel_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f" \
  -d '{
    "nif": "B12345674",
    "legal_name": "Mi Empresa SL",
    "entity_type": "LEGAL_ENTITY",
    "address": {
      "street": "Calle Mayor",
      "number": "10",
      "postal_code": "28001",
      "city": "Madrid",
      "province": "Madrid"
    },
    "legal_form": "SL",
    "legal_representative": {
      "full_name": "Ada Lovelace",
      "nif": "12345678Z",
      "address": {
        "street": "Calle Mayor", "number": "10",
        "postal_code": "28001", "city": "Madrid", "province": "Madrid"
      }
    },
    "trade_name": "Mi Empresa",
    "default_main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" },
    "aeat_environment": "TEST"
  }'
```

### The fields that decide the flow

The complete request body — every field, its type, whether it is required and its
default — is in [Create a company](/companies/createCompany). Four of them change what
the call *does*, and are worth understanding before you send it:

- **`entity_type`** (`INDIVIDUAL` or `LEGAL_ENTITY`) is **immutable after creation**. It must match the NIF: an individual has a DNI or NIE, an entity a NIF starting with the letter of its legal form; a mismatch answers `422` [`ENTITY_TYPE_INCONSISTENT_WITH_NIF`](/errors/ENTITY_TYPE_INCONSISTENT_WITH_NIF).
  It is not a label: it decides whether `legal_form` and `legal_representative` are
  accepted at all. Getting it wrong means deleting the NIF and starting again.
- **`aeat_environment`** picks which AEAT environment the NIF is registered against,
  and with it the whole billing branch — see
  [The `aeat_environment` field decides the flow](#the-aeat_environment-field-decides-the-flow).
- **`activate`** is what separates "a NIF that can invoice" from "a NIF on file".
  Left at its default the call also seeds the series and tax defaults; `false` creates
  the profile only, and you switch it on later with the
  [activations endpoint](#switching-a-nif-on-in-test-or-live). A `numbering` block
  without an activation is rejected with `422` [`NUMBERING_REQUIRES_ACTIVATION`](/errors/NUMBERING_REQUIRES_ACTIVATION) — there
  are no series yet to number.
- **`default_irpf_rate`** is a declaration, not a default. See the warning below. It must be a rate the company can bear by its NIF: a company (`B…`) declaring 15 % answers `422` [`IRPF_RATE_NOT_FOR_CORPORATE_ISSUER`](/errors/IRPF_RATE_NOT_FOR_CORPORATE_ISSUER) (see [IRPF](/guides/amounts-and-rounding#irpf)).

<Callout type="warn">
  **Never preselect an IRPF rate.** A company created without `default_irpf_rate`
  withholds nothing *because nobody declared a rate*; a company with `0` withholds
  nothing *because its owner declared so*. They invoice the same, but only one is a
  declaration — do not fill the absent case in with a number of your own.
</Callout>

<Callout type="info">
  **The NIF must exist in the AEAT census — in the sandbox too.** A syntactically valid NIF
  that is not registered is `422` [`NIF_VALIDATION_INVALID`](/errors/NIF_VALIDATION_INVALID); one registered
  as revoked is `422` [`NIF_REVOCADO`](/errors/NIF_REVOCADO). Use your client's real NIF, even to
  rehearse.
</Callout>

### The `aeat_environment` field decides the flow

| Case | Result |
|------|--------|
| `aeat_environment: "TEST"` | Created immediately, free. Sandbox NIF; invoices reach VeriFactu test. `201` + the company, with every field `GET /v1/companies/{company_id}` returns, plus its `series`. |
| `aeat_environment: "PROD"`, account **entitled to Live** — a card on file, or an active paid plan | Created immediately as a production NIF. Real AEAT emission stays blocked until the VeriFactu representation is signed for this NIF. `201` + the company. |
| `aeat_environment: "PROD"`, account with **no card on file** | No company is created. `402` [`CHECKOUT_REQUIRED`](/errors/CHECKOUT_REQUIRED), with **no checkout URL**. |
| `aeat_environment: "PROD"`, account **past due** | No company is created. `402` [`PAYMENT_REQUIRED`](/errors/PAYMENT_REQUIRED). Settle the outstanding invoice first. |
| `aeat_environment: "PROD"`, account on a **fixed-quota plan still on trial** | No company is created. `402` [`PLAN_ACTIVATION_REQUIRED`](/errors/PLAN_ACTIVATION_REQUIRED): activate the plan you already chose; no card checkout applies, because the quota already includes this NIF. |

<Callout type="warn">
  **This endpoint never starts a charge.** A [`CHECKOUT_REQUIRED`](/errors/CHECKOUT_REQUIRED) here carries no
  `checkout_url`: capturing a card is a browser flow, and an API key has no browser to
  return from. Create the company in `TEST` (or with `activate: false`) and switch it
  on in Live with the [activations endpoint](#switching-a-nif-on-in-test-or-live),
  which is the only door that opens a checkout.
</Callout>

### Switching a NIF on in Test or Live

`POST /v1/companies/{company_id}/activations` —
**`companies:write`**. This is how a NIF that already exists reaches Live: creation
only ever switches a NIF on in one mode, so a NIF created in Test — how every
provisioned account starts — has no other route. The mode travels in the body and is
never taken from the key's environment, so a `beel_sk_test_…` key can switch a NIF on
in Live — on purpose: a live key can only be created once the account has Live, so the
first activation in Live has to come from a test key (or from the dashboard).

<Callout type="warn">
  **Switching a NIF on in Live is billable, whatever key you use.** A test key cannot
  create Live invoices, but it can create a Live NIF, and that NIF counts towards your
  subscription from that moment.
</Callout>

```bash
curl -X POST https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000/activations \
  -H "Authorization: Bearer beel_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f8c1e10-3b2a-4c9d-8e7f-1a2b3c4d5e6f" \
  -d '{
    "environment": "PROD",
    "success_url": "https://your-app.example/billing/return?session={CHECKOUT_SESSION_ID}",
    "cancel_url": "https://your-app.example/billing"
  }'
```

It returns `201`. `TEST` is immediate and free. `PROD` is immediate when the account is
entitled to Live, and the NIF is added to the existing subscription. Otherwise nothing is
switched on and the call answers `402`, with the same three codes as creation:

- [`CHECKOUT_REQUIRED`](/errors/CHECKOUT_REQUIRED) — no card on file. When you passed `success_url`
  and `cancel_url`, `error.details.checkout_url` is the checkout to hand to the account
  holder. `success_url` may embed Stripe's `{CHECKOUT_SESSION_ID}` placeholder.
- [`PAYMENT_REQUIRED`](/errors/PAYMENT_REQUIRED) — past due. Settle the outstanding invoice first.
- [`PLAN_ACTIVATION_REQUIRED`](/errors/PLAN_ACTIVATION_REQUIRED) — a fixed-quota plan still on trial.
  Activate the plan.

The call is idempotent: repeating it neither opens a second checkout nor adds a second
subscription item, and answers `already_active: true`. It also cancels a pending switch-off
(`scheduled_deactivation_cancelled: true`), with nothing charged or credited.

Switching a NIF **off** is `DELETE …/activations?environment=TEST` or `…=PROD`;
`environment` is required (`400` [`MISSING_PARAMETER`](/errors/MISSING_PARAMETER) without it). In
Test it is immediate. In Live it is *scheduled*: the response carries an `effective_at` and
the NIF keeps invoicing until then. Nothing is refunded.

### When creation fails, read the code before retrying

The status codes and their bodies are on the operation page; what they *mean for your
next call* is not:

- **`409` [`NIF_ALREADY_REGISTERED`](/errors/NIF_ALREADY_REGISTERED)** is not a dead end. The
  existing id travels in `error.details.company_id` — switch *that* NIF on instead of
  creating it again.
- **`409` [`NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT`](/errors/NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT)**
  is structural: a NIF can only issue in production from one account. No retry fixes
  it; the other account has to release it first.
- **[`NIF_VALIDATION_INVALID`](/errors/NIF_VALIDATION_INVALID)** and **[`NIF_REVOCADO`](/errors/NIF_REVOCADO)**
  are about the NIF itself, not your request: no retry fixes them.
- **`502` [`EXTERNAL_SERVICE_ERROR`](/errors/EXTERNAL_SERVICE_ERROR)** means the AEAT census could not
  be reached to validate the NIF. Nothing was created; repeat the same request later, with
  the same `Idempotency-Key`.
- **`422` [`POSTAL_CODE_INVALID_ES`](/errors/POSTAL_CODE_INVALID_ES)**: a Spanish postal code, in
  `address` or in `legal_representative.address`, does not have 5 digits. Nothing was
  created. Other countries' postal codes are free-form.
- A **`403`** never distinguishes "you cannot reach it" from "it does not exist". Do
  not treat it as a signal that the NIF is free.

The company is created with everything you send: `trade_name`, the whole address (floor,
door and country included) and the legal representative's.

## List companies

`GET /v1/accounts/{account_id}/companies` — requires the **`companies:list`** scope.
Returns a **paginated** list of the companies (NIFs) of that account (query params
`page`, `limit`, `search`, `include`; primary company first). Always paginated — it
scales to thousands of NIFs. An account with no NIF yet returns an empty page, not an
error. See [the reference](/companies/listCompanies) for the response schema.

```bash
curl https://app.beel.es/api/v1/accounts/3fa85f64-5717-4562-b3fc-2c963f66afa6/companies \
  -H "Authorization: Bearer beel_sk_live_xxx"
```

Add `?include=readiness` to get each company's
[issuing-readiness](#is-this-nif-ready-to-issue) block in the same call.

Each item is a company object — its full shape is in
[List companies](/companies/listCompanies). Three of its fields are read wrong often
enough to be worth calling out:

- **`id` is the `{company_id}`** you put in the path of every later call that touches
  this NIF. It is the only handle; nothing addresses a company by its `nif` string.
- **`environment` is deprecated and cannot be trusted.** A single scalar cannot express
  a NIF that is switched on in both modes. Read the `in_test` / `in_prod` pair instead.
- **`default_irpf_rate` absent is not `default_irpf_rate: 0`.** Absent means nobody ever
  declared a rate; `0` means someone declared exemption. Do not coalesce one into the
  other when you render or re-send it. It is read-only here — change it with
  `PUT /v1/companies/{company_id}/tax-configuration`.

<Callout type="info">
  **Two independent axes — don't conflate them.** The activation pair
  (`in_test` / `in_prod`) is the data/billing mode, matching the dashboard's Test/Live
  switch. AEAT emission capability is a separate axis (`account_state`,
  `verifactu_status`, and the `readiness` block). A company can bill in Live and still
  have AEAT emission disabled until its VeriFactu representation is signed.
</Callout>

### Per-company stats

`GET /v1/accounts/{account_id}/companies/stats` — **`companies:list`**. Returns
invoice aggregates per company (`company_id`, `invoice_count`, `last_invoice_at`),
kept separate from the list so the company switcher stays cheap.

## Read a single company

`GET /v1/companies/{company_id}` — requires **`companies:read`**. Returns the same
company object as above, including VeriFactu status. The NIF in the path is the only
source of context: the account that owns it is derived from it, so there is no pair to
keep coherent.

### Is this NIF ready to issue?

`GET /v1/companies/{company_id}/issuing-readiness` —
**`companies:read`**. Returns `ready` plus, when it is `false`, the exact `blockers`, for
the environment of your key, and a separate `verifactu` block. How to read it, and each
blocker's meaning and fix, is in [Is a NIF ready to invoice?](/multi-nif#is-a-nif-ready-to-invoice);
the three VeriFactu ones in detail in
[Enabling VeriFactu](/verifactu/enabling-verifactu#what-blocks-issuing). Readiness is per NIF; it does
**not** cover the payer account's quota or a specific invoice's payload.

## Update a company

`PATCH /v1/companies/{company_id}` — requires
**`companies:write`**. Most fields (trade name, address, legal representative, contact
details, IBAN, PDF template…) are editable — see
[the reference](/companies/patchCompanyById) for the full editable set. The exceptions:

- **`nif`, `entity_type` and `legal_form` are immutable.** Sending a different value
  answers `422` [`IMMUTABLE_NIF`](/errors/IMMUTABLE_NIF), [`IMMUTABLE_ENTITY_TYPE`](/errors/IMMUTABLE_ENTITY_TYPE)
  or [`IMMUTABLE_LEGAL_FORM`](/errors/IMMUTABLE_LEGAL_FORM), and nothing is written; sending the value it
  already has is not a change.
- **`legal_name`** only changes together with an AEAT census re-validation. For a legal
  entity the census identifies the company by its NIF alone, so the name you send cannot
  make it fail; for an `INDIVIDUAL` the name must match the census. If the census cannot be
  reached, the change is not rejected: the response is `200` with the new name stored, and
  the check is repeated in the background.
- **A Spanish postal code** must have 5 digits, in `address` and in
  `legal_representative.address` — otherwise `422` [`POSTAL_CODE_INVALID_ES`](/errors/POSTAL_CODE_INVALID_ES).
- **Access `VIEW`** over the NIF reads it but cannot change it: a write answers
  `403` [`COMPANY_READ_ONLY`](/errors/COMPANY_READ_ONLY).

```bash
curl -X PATCH https://app.beel.es/api/v1/companies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer beel_sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "trade_name": "Mi Empresa (Madrid)",
    "address": {
      "street": "Gran Vía",
      "number": "1",
      "postal_code": "28013",
      "city": "Madrid",
      "province": "Madrid",
      "country_code": "ES"
    }
  }'
```

## Delete a company

`DELETE /v1/companies/{company_id}` — requires
**`companies:write`**. Returns `204`. You **cannot delete your primary company**
(`400` [`CANNOT_DELETE_PRIMARY`](/errors/CANNOT_DELETE_PRIMARY)), nor a company that holds invoices in Live.

A company that is **switched on in Live** cannot be deleted either: it returns
`409` [`COMPANY_ACTIVE_IN_PRODUCTION`](/errors/COMPANY_ACTIVE_IN_PRODUCTION). Leaving Live is a *scheduled* deactivation — the
current cycle is charged and served in full — so switch it off first and delete it
once the deactivation takes effect. Companies never activated, or active only in
Test, are deleted right away.

## Signing the VeriFactu representation

A production NIF only reaches the real AEAT once its VeriFactu representation is
generated, signed by the NIF's holder and submitted. That flow lives under
`/v1/companies/{company_id}/representation`:

| Step | Call | Notes |
| --- | --- | --- |
| Generate | `POST …/representation` | Either key. Needs a complete fiscal identity — for a legal entity, the legal representative's full address too; otherwise [`PROFILE_INCOMPLETE`](/errors/PROFILE_INCOMPLETE), with the missing fields in `error.details`. |
| Download | `GET …/representation/document` | Either key. A short-lived `download_url` to the PDF the holder signs. |
| Submit the signed copy | `POST …/representation/submit` | **Live key only** — the representation has no test mode (`400` [`SIGNING_ONLY_IN_LIVE`](/errors/SIGNING_ONLY_IN_LIVE) with a test key). `multipart/form-data` with the PDF in `file`. A PDF without a valid signature is `400` [`PDF_SIGNATURE_INVALID`](/errors/PDF_SIGNATURE_INVALID); a file that is not a PDF, `400` [`PDF_INVALID_EXTENSION`](/errors/PDF_INVALID_EXTENSION). |
| Status | `GET …/representation` | Never fails: `NOT_STARTED`, `PDF_GENERATED`, `ACTIVE` or `CANCELLED`. `SUBMITTED` is only the acknowledgement of the submit call, and `ERROR` is reserved. |
| Cancel | `DELETE …/representation` | Cancels an **active** (signed) representation. Before that there is nothing to cancel, and it answers `400`. |

The document authorises BeeL. to submit the NIF's billing records to AEAT on the
taxpayer's behalf — this is how BeeL. submits without the taxpayer handing over a
digital certificate. The holder (the self-employed person, or the legal representative of
a company) signs the PDF electronically with their own certificate, once. Everything
else — generating, downloading, uploading and checking the status — is an API call, so a
new issuer can be onboarded entirely from your product except for that signature.

The holder signs; whoever holds `OPERATE` over the NIF — the holder, or you as its
provisioner — uploads the signed copy. See
[Generate the representation document](/companies/generateCompanyRepresentation) and [Submit the signed representation document](/companies/submitCompanyRepresentation).

## Related

<Related>

- [Target this company in requests](/multi-nif#where-each-identifier-goes) — put the company `id` in the path: `/v1/companies/{company_id}/…`
- [Give teammates access](/multi-nif/members-and-grants) — grant a member `VIEW` or `OPERATE` access to specific companies

</Related>

---

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