---
title: "Switch a company on"
method: POST
path: "/v1/companies/{company_id}/activations"
tags: ["CompanyActivations"]
---

# Switch a company on

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

## Request

- Base URL: `https://app.beel.es/api`
- URL: `https://app.beel.es/api/v1/companies/{company_id}/activations`
- Auth: HTTP bearer

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

## Modes and billing

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

## Idempotency and pending switch-offs

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

## Headers

- `Idempotency-Key` string

## Request body

- ActivateCompanyRequest
  - `environment` 'TEST' | 'PROD', required: Mode a record lives in — its Test/Live twin. It decides where invoices, customers and quota are accounted. For a company it also decides which AEAT its NIF is registered against: switching a company on in `PROD` is what registers it with the real AEAT, so `aeat_environment` is that same mode and uses this same enum.
  - `success_url` string: Where Stripe returns after the card is captured. Only used when switching on in Live with no card on file. May embed Stripe's `{CHECKOUT_SESSION_ID}` template, which is why it is a plain string and not a `uri`: the braces are not legal URI characters.
  - `cancel_url` string, uri: Where Stripe returns if the checkout is abandoned.
  - `billing_period` 'MONTHLY' | 'ANNUAL': Nominal billing period of a subscription.

## Response `201`

The NIF is switched on in that mode.

- CompanyActivationResponse: The envelope every successful JSON response of the BeeL. API is wrapped in. There are no bare resources in v1 and none are planned: the payload always hangs off `data`. - A **single resource** is an object in `data`. - A **collection** hangs off a named key inside `data`, together with its `pagination`, also inside `data` — `data: {invoices: [...], pagination: {...}}`. - A collection carries `pagination` unless its operation declares itself a **closed catalogue**: a fixed, bounded list with nothing to page through. The declaration is explicit in the operation; a missing `pagination` is never something to infer. - `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a documented variant of pagination, not another envelope. Putting the array straight into `data` with `pagination` as its sibling is the shape a v2 would adopt; v1 is not being flipped to it.
  - `success` boolean, required
  - `data` object, required: The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
    - `company_id` string, uuid, required
    - `environment` 'TEST' | 'PROD', required: Mode a record lives in — its Test/Live twin. It decides where invoices, customers and quota are accounted. For a company it also decides which AEAT its NIF is registered against: switching a company on in `PROD` is what registers it with the real AEAT, so `aeat_environment` is that same mode and uses this same enum.
    - `already_active` boolean, required: The company was already on in that mode and nothing was written.
    - `scheduled_deactivation_cancelled` boolean, required: The call removed a pending switch-off. Comes with `already_active: true`, since the activation never stopped being live. Nothing was charged or credited.
  - `meta` ResponseMeta
    - `timestamp` string, date-time
    - `request_id` string

## Other responses

- `400`: Bad request. From a dashboard session, a `BeeL-Environment` header whose value is not a known environment answers `ENVIRONMENT_HEADER_INVALID`, with the header named in `error.details` (`{"BeeL-Environment": "invalid"}`); it is never read as production. `BUSINESS_RULE_VIOLATION` is the generic answer to an operation the resource does not admit in its current state when that state has no code of its own. It carries no `details`; read the resource again before retrying.
- `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.
- `402`: Switching on in Live requires payment. `CHECKOUT_REQUIRED` means there is no card on file yet; `error.details.checkout_url` carries the checkout when `success_url` and `cancel_url` were supplied. `PAYMENT_REQUIRED` means billing exists but is unpaid (payment retries are exhausted), and the outstanding invoice has to be settled first. A failed charge that is still being retried does not block Live.
- `403`: `ACTIVE_COMPANY_NOT_ACCESSIBLE` — your account does not own or manage this NIF, or does not hold write access over it. A NIF that does not exist answers exactly the same. `NOT_BILLING_OWNER` — switching a NIF on in Live requires being the billing subject of the account. `BILLING_MANAGED_BY_PROVIDER` — the account's billing is run by its provider, so the switch has to be requested from them; retrying will not help. `CONTACT_SALES_REQUIRED` — the account is on the API plan (billed per NIF) with no agreed price and has two or more active companies: going Live needs a per-NIF price agreed with sales first, and nothing is switched on and no checkout is opened. Not a hard error: `error.details.reason` is `LIVE_WITH_MULTIPLE_NIFS` and `error.details.salesUrl` is where to talk to sales. It applies from the first NIF in Live; with a single active company, or once sales configures the agreed price, the same request goes through. Answered before the `409` of a NIF live in another account and before any `402`.
- `409`: The NIF cannot be switched on in Live. Branch on `error.code`: * `NIF_PROD_ALREADY_ACTIVE_IN_ANOTHER_ACCOUNT` — another account already has this NIF in Live. A NIF can only invoice in Live from one account, so the other one has to switch it off first. * `NIF_LIMIT_REACHED` — the account's agreement caps how many NIFs can be active in Live, and the cap is full. It applies to a NIF that does not hold a seat yet: a new one, one whose switch-off already took effect, or one whose pending switch-off this call would cancel. `error.details` is empty; the cap travels in `error.message`. Switch another NIF off in Live, or contact BeeL to raise the cap. Test is never capped. Nothing is switched on in either case.
- `422`: `ACCOUNT_HAS_NO_HOLDER` — the account was provisioned without a person and nobody has claimed it yet, so there is no holder to attach the activation to. Issue a claim token with `POST /v1/accounts/{account_id}/claim-tokens` first. `CHECKOUT_URLS_REQUIRED` — switching on in Live with no card on file opens a checkout, and that needs both `success_url` and `cancel_url`. `ANNUAL_PLAN_NOT_AVAILABLE` — `billing_period: ANNUAL` was requested but this account cannot buy the annual plan (a negotiated rate, an enterprise contract, or no annual plan offered); switch on without `billing_period` to buy the monthly plan. Nothing was activated.
- `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.
- `500`: Internal server error
- `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.

---

[API](https://skmtc.dev/beel/apis/beel-public-api.md) · [All operations](https://skmtc.dev/beel/apis/beel-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/beel/apis/beel-public-api/revisions/ffbc0ae31f79?raw)
