NewProvince is only required for addresses in Spain
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

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.

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. Everything you then do to one NIF is addressed by the NIF itself: /v1/companies/{company_id}/…. See Where each identifier goes.

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.

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. 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. 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.
  • 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. A numbering block without an activation is rejected with 422 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 (see IRPF).

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.

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; one registered as revoked is 422 NIF_REVOCADO. Use your client's real NIF, even to rehearse.

The aeat_environment field decides the flow

CaseResult
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 planCreated 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 fileNo company is created. 402 CHECKOUT_REQUIRED, with no checkout URL.
aeat_environment: "PROD", account past dueNo company is created. 402 PAYMENT_REQUIRED. Settle the outstanding invoice first.
aeat_environment: "PROD", account on a fixed-quota plan still on trialNo company is created. 402 PLAN_ACTIVATION_REQUIRED: activate the plan you already chose; no card checkout applies, because the quota already includes this NIF.

This endpoint never starts a charge. A 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, which is the only door that opens a checkout.

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

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.

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 — 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 — past due. Settle the outstanding invoice first.
  • 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 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 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 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 and NIF_REVOCADO are about the NIF itself, not your request: no retry fixes them.
  • 502 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: 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 for the response schema.

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 block in the same call.

Each item is a company object — its full shape is in List companies. 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.

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.

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?; the three VeriFactu ones in detail in Enabling VeriFactu. 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 for the full editable set. The exceptions:

  • nif, entity_type and legal_form are immutable. Sending a different value answers 422 IMMUTABLE_NIF, IMMUTABLE_ENTITY_TYPE or 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.
  • Access VIEW over the NIF reads it but cannot change it: a write answers 403 COMPANY_READ_ONLY.
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), 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. 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:

StepCallNotes
GeneratePOST …/representationEither key. Needs a complete fiscal identity — for a legal entity, the legal representative's full address too; otherwise PROFILE_INCOMPLETE, with the missing fields in error.details.
DownloadGET …/representation/documentEither key. A short-lived download_url to the PDF the holder signs.
Submit the signed copyPOST …/representation/submitLive key only — the representation has no test mode (400 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; a file that is not a PDF, 400 PDF_INVALID_EXTENSION.
StatusGET …/representationNever fails: NOT_STARTED, PDF_GENERATED, ACTIVE or CANCELLED. SUBMITTED is only the acknowledgement of the submit call, and ERROR is reserved.
CancelDELETE …/representationCancels 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 and Submit the signed representation document.