# Provision an account API Reference

Provisions a new account on BeeL and, when it is born with a holder, returns a single-use
`claim_token` to deliver so they can set a password and take ownership.

- **`email`:** send it to create the account with a holder. Omit it and the account is
  created with no person at all, no `person_id` and no `claim_token`; a holder can be
  added later with `POST /v1/accounts/{account_id}/claim-tokens`.
- **`tax_profile`:** send it and the account comes back ready to invoice, with its NIF,
  default invoice series and VeriFactu configuration set up. Omit it and the account's
  company is created without a NIF until its holder registers one. Either way its
  `company_id` is in the response.
- **`access_level`:** the access you retain over the account. Defaults to `NONE`;
  `OPERATE` requires a `tax_profile`.
- **`external_ref`:** the idempotency key. Resending the same one returns the existing
  account rather than creating a second.
- **Entitlement:** requires `manage_accounts`.

## Reactivation

If you previously ended your management of this account
(`DELETE /v1/accounts/{account_id}/management`) and its holder has not claimed it yet,
provisioning the same email reactivates that account instead of creating a new one. The
same account, holder, NIFs and invoices come back under your management, with the
`external_ref` and `access_level` of this request, and it counts towards your billable
usage again. Once the holder has claimed the account it is theirs, and only they can
grant you access again.


## POST /v1/accounts

**Provision an account**

Provisions a new account on BeeL and, when it is born with a holder, returns a single-use
`claim_token` to deliver so they can set a password and take ownership.

- **`email`:** send it to create the account with a holder. Omit it and the account is
  created with no person at all, no `person_id` and no `claim_token`; a holder can be
  added later with `POST /v1/accounts/{account_id}/claim-tokens`.
- **`tax_profile`:** send it and the account comes back ready to invoice, with its NIF,
  default invoice series and VeriFactu configuration set up. Omit it and the account's
  company is created without a NIF until its holder registers one. Either way its
  `company_id` is in the response.
- **`access_level`:** the access you retain over the account. Defaults to `NONE`;
  `OPERATE` requires a `tax_profile`.
- **`external_ref`:** the idempotency key. Resending the same one returns the existing
  account rather than creating a second.
- **Entitlement:** requires `manage_accounts`.

## Reactivation

If you previously ended your management of this account
(`DELETE /v1/accounts/{account_id}/management`) and its holder has not claimed it yet,
provisioning the same email reactivates that account instead of creating a new one. The
same account, holder, NIFs and invoices come back under your management, with the
`external_ref` and `access_level` of this request, and it counts towards your billable
usage again. Once the holder has claimed the account it is theirs, and only they can
grant you access again.

### Authentication

Accepts any of:

- `ApiKeyAuth` (HTTP bearer, token format `beel_sk_*`)

### Parameters

- **Idempotency-Key** (optional) in header `string`: Idempotency key to prevent duplicates in sensitive operations. - Any unique client-generated string (e.g. an order id). A UUID also works but is not required - Allowed characters: letters, digits, `_` and `-` (max 255 chars) - Retrying with the same key replays the first response when it was a success (2xx) or a server error (5xx): same status and body, plus the header `Idempotency-Replay: true`. After a 5xx, check whether the operation took effect before retrying with a **new** key - A 4xx is not stored: the key is released, so the corrected request can reuse it - Stored responses expire 24 hours after processing The key is scoped per user and environment, and bound to the request body, so retrying after a network timeout replays the stored response instead of repeating the operation. | Status | Code | When | |---|---|---| | `400` | `INVALID_IDEMPOTENCY_KEY` | The key breaks the format rules above. | | `409` | `IDEMPOTENCY_KEY_PROCESSING` | The first request is still in flight. Wait for the `Retry-After` seconds (2) and retry with the same key. | | `409` | `IDEMPOTENCY_KEY_MISMATCH` | The key was already used with a **different** body. Use a new key. |

### Request Body

Required.

**Content `application/json`:**

- **email** `string` (email): Optional. Email address of the account holder, used as their login. Omit it to create the account **without a person**: no login, no `person_id` and no `claim_token`. You can add the holder later with `POST /v1/accounts/{account_id}/claim-tokens`. Required when `send_email` is `true` (there is nobody to write to otherwise) — else `422`.
- **display_name** (required) `string`: Human-readable name for the account. Must not be blank.
- **external_ref** (required) `string`: Your own identifier for this account in your system. Used as an idempotency key: re-provisioning with the same `external_ref` returns the existing account (201, not 409).
- **language**: Preferred language for the account holder. Defaults to `es`.
- **access_level**: Optional. The access you retain over this account after provisioning. Defaults to `NONE` (you cover their subscription but cannot access their data). Change it later via PATCH /v1/accounts/{account_id}/access-level. This field was previously named `access`; the old name is still accepted as an alias for backwards compatibility and will be withdrawn in a future major version — send `access_level`.
- **tax_profile**: Optional fiscal identity. When present, the account is created **ready to invoice** in one call: its company record, a default invoice series and VeriFactu config are set up atomically, and the response returns `company_id` (the value for the `BeeL-Active-Company` header when issuing invoices). Omit it to create an empty account the holder completes on claim. **Required when `access_level` is `OPERATE`** (issuing on their behalf needs a NIF) — else `422`.
- **send_email** `boolean`: Optional. When `true`, BeeL emails the account holder a claim link (`/reclamar?token=...`) so they can set their password and take ownership. Defaults to `false`: by default you receive the `claim_token` in the response and deliver it yourself. Requires a deliverable `email`: sending `true` without one returns `422`.

**Example `provision_account_with_company`** — Provision an account with its first company:

```json
{
  "email": "hola@estudio-ejemplo.example.com",
  "display_name": "Estudio Ejemplo",
  "external_ref": "cliente-4821",
  "language": "es",
  "access_level": "OPERATE",
  "tax_profile": {
    "nif": "12345678Z",
    "legal_name": "María López Fernández",
    "entity_type": "INDIVIDUAL",
    "address": {
      "street": "Calle Mayor",
      "number": "15",
      "floor": "2",
      "door": "B",
      "postal_code": "28013",
      "city": "Madrid",
      "province": "Madrid",
      "country_code": "ES"
    },
    "default_main_tax": {
      "type": "IVA",
      "percentage": 21,
      "regime_key": "01"
    },
    "default_irpf_rate": 15
  },
  "send_email": false
}
```

**Example `provision_account_minimal`** — Provision an account without a holder yet:

```json
{
  "display_name": "Taller Ejemplo",
  "external_ref": "cliente-4822"
}
```

### Responses

#### 201: Account provisioned successfully. **Business idempotency** by `external_ref`: resending the same `external_ref` returns **201 with the same account id** (not 409). While the account is still unclaimed, a claim link that is still valid is **never revoked**: the response comes back with `claim_token` and `claim_url` `null` and `claim_link_already_issued: true`, and no email is re-sent. Only when no valid link remains (expired, or none was ever issued) is a fresh `claim_token` issued. To replace a valid link on purpose, use `POST /v1/accounts/{account_id}/claim-tokens`. For **exact response idempotency** (same body, same token on retries) send an `Idempotency-Key` header — the response is cached for 24h.

**Headers:**

- `Location` `string`: URI of the created resource — its canonical GET (`/v1/companies/{company_id}/...` or `/v1/accounts/{account_id}/...`).

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: true)
- **data** (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.
- **meta** `ResponseMeta`
- **data** `ProvisionAccountResult`: Result of a successful account provision. `claim_token` is a single-use secret to deliver to the account holder so they can set their password and take ownership. It is returned only once; `null` if the account was already claimed, `null` when the account was provisioned without `email` (there is no holder to hand it to yet), and `null` when a repeated provision found a claim link still valid (`claim_link_already_issued: true`). `claim_url` is the ready-to-use claim link BeeL builds for the current environment; `status` disambiguates a `null` `claim_token` (already claimed) from an error.

#### 400: Bad request

**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": "BAD_REQUEST",
    "message": "Invalid request"
  },
  "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: Authenticated but not allowed. Ten causes, told apart by `error.code`. The list is
**closed**: every 403 this API returns carries one of these ten, so you can branch on
them exhaustively.

- `INSUFFICIENT_SCOPE` — the credential lacks a scope the operation requires;
  `error.details.missing_scopes` names them as a single comma-separated string (for
  example `"invoices:write,customers:read"`), not as an array; `required_scopes` has the
  same shape and lists every scope the operation needs. Retrying will not help: mint a key
  that holds them.
- `COMPANY_READ_ONLY` — the scope is there, but your access level over that NIF only
  lets you read it.
- `ACCOUNT_MANAGEMENT_FORBIDDEN` — the scope is there, but your role over the account,
  or the management relationship you hold over it, does not cover this operation.
- `ACCOUNT_NOT_ACCESSIBLE` — the account is not yours to reach, which is also the answer
  when it does not exist, so existence is never disclosed.
- `ACTIVE_COMPANY_NOT_ACCESSIBLE` — the same, for a NIF: the company in the path, or the
  one named by `BeeL-Active-Company`, is not one this credential may reach — and again,
  this is also the answer when it does not exist.
- `COMPANY_ACCESS_REVOKED` — your access to the NIF was withdrawn while the request was
  already in flight, so the write was rejected and nothing was recorded. Retrying will
  not help until the access is granted again.
- `LIVE_CREDENTIAL_REQUIRED` — the operation changes the real account and the call came
  from a test API key (`beel_sk_test_…`). Use your live key or the dashboard.
- `FEATURE_NOT_AVAILABLE` — your subscription does not include the entitlement the
  operation needs; `error.details.feature_code` names it. This gate runs **before** the
  scope gate, so for such an operation you never see `INSUFFICIENT_SCOPE` first.
- `NO_ACTIVE_ACCOUNT` — the credential does not resolve to an account, so no scope can be
  evaluated against one. Fail-closed, not a permission that can be granted to you.
- `OPERATION_REQUIRES_SESSION` — the operation is available only from the web session; no
  API key and no OAuth2 token can perform it, whatever scopes it holds.


**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": "FORBIDDEN",
    "message": "You do not have permission to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 409: Conflict. Returned when the email is already used by an existing BeeL. account that this request can neither reuse idempotently (same `external_ref`) nor reactivate. Two distinct cases:

- `PROVISIONING_ACCOUNT_CLAIMED` — you did manage this account and ended it, but its holder has already claimed it. It is theirs now: ask them to grant you access from their account. Provisioning cannot take it back.
- `PROVISIONING_EMAIL_ALREADY_REGISTERED` — the email belongs to an account you never managed, one someone else manages, or one whose management someone else ended. Use a different email or contact support.

**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 error. Specific to this operation:

- `PROVISIONING_TAX_PROFILE_REQUIRED` — `access_level` is `OPERATE` and there is no
  `tax_profile`: invoicing on the account's behalf needs its NIF, so it is refused here rather
  than at the first invoice.
- `PROVISIONING_EMAIL_REQUIRED` — `send_email` is `true` and there is no `email` to send the
  claim link to.

Any other field failure answers `VALIDATION_ERROR`, 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": "PROVISIONING_TAX_PROFILE_REQUIRED",
    "message": "With access_level OPERATE you must send tax_profile: invoicing on the account's behalf needs its NIF."
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 429: Rate limit exceeded

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

#### 502: `EXTERNAL_SERVICE_ERROR` — the AEAT census could not be reached to validate the NIF. Only happens when the request carries a `tax_profile`; the account was not created. Nothing was
stored. It is transient; repeat
the same request later (with the same `Idempotency-Key` if you sent one). No
`Retry-After` header is sent.


**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": "EXTERNAL_SERVICE_ERROR",
    "message": "A technical error occurred. Please try again later.",
    "details": {}
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  },
  "type": "https://docs.beel.es/errors/EXTERNAL_SERVICE_ERROR",
  "title": "EXTERNAL_SERVICE_ERROR",
  "detail": "A technical error occurred. Please try again later.",
  "instance": "/api/v1/accounts"
}
```

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

## ProvisionAccountRequest

Request to provision a new account. You retain management access at the level in `access_level` (defaults to `NONE`).

There are two ways to integrate and both are supported. **With `email`** the account is born with a holder (a person who can log in) and the response carries a `claim_token` / `claim_url` to hand over. **Without `email`** the account and its NIF are created with **no person at all** — for platforms whose self-employed workers will never use BeeL. themselves — and the response carries no token. Inviting someone to claim it is a deferred, optional step: `POST /v1/accounts/{account_id}/claim-tokens`. Nothing is invented: BeeL. never fabricates a placeholder email.

- **email** `string` (email): Optional. Email address of the account holder, used as their login. Omit it to create the account **without a person**: no login, no `person_id` and no `claim_token`. You can add the holder later with `POST /v1/accounts/{account_id}/claim-tokens`. Required when `send_email` is `true` (there is nobody to write to otherwise) — else `422`.
- **display_name** (required) `string`: Human-readable name for the account. Must not be blank.
- **external_ref** (required) `string`: Your own identifier for this account in your system. Used as an idempotency key: re-provisioning with the same `external_ref` returns the existing account (201, not 409).
- **language**: Preferred language for the account holder. Defaults to `es`.
- **access_level**: Optional. The access you retain over this account after provisioning. Defaults to `NONE` (you cover their subscription but cannot access their data). Change it later via PATCH /v1/accounts/{account_id}/access-level. This field was previously named `access`; the old name is still accepted as an alias for backwards compatibility and will be withdrawn in a future major version — send `access_level`.
- **tax_profile**: Optional fiscal identity. When present, the account is created **ready to invoice** in one call: its company record, a default invoice series and VeriFactu config are set up atomically, and the response returns `company_id` (the value for the `BeeL-Active-Company` header when issuing invoices). Omit it to create an empty account the holder completes on claim. **Required when `access_level` is `OPERATE`** (issuing on their behalf needs a NIF) — else `422`.
- **send_email** `boolean`: Optional. When `true`, BeeL emails the account holder a claim link (`/reclamar?token=...`) so they can set their password and take ownership. Defaults to `false`: by default you receive the `claim_token` in the response and deliver it yourself. Requires a deliverable `email`: sending `true` without one returns `422`.

## SuccessResponse

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** (required) `boolean`: No description (example: true)
- **data** (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.
- **meta** `ResponseMeta`

## ResponseMeta

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

## ProvisionAccountResult

Result of a successful account provision. `claim_token` is a single-use secret to deliver to the account holder so they can set their password and take ownership. It is returned only once; `null` if the account was already claimed, `null` when the account was provisioned without `email` (there is no holder to hand it to yet), and `null` when a repeated provision found a claim link still valid (`claim_link_already_issued: true`). `claim_url` is the ready-to-use claim link BeeL builds for the current environment; `status` disambiguates a `null` `claim_token` (already claimed) from an error.

- **person_id** `string` (uuid): The account holder. `null` when the account was provisioned **without `email`**: it has no person yet, and gains one when a claim token is issued.
- **account_id** (required) `string` (uuid): No description
- **status** (required): Lifecycle stage of the account right after this call. A newly provisioned account is `PROVISIONED` (not yet claimed). Combined with `claim_token`, it is self-explanatory: a `null` `claim_token` with `status: CLAIMED`/`ACTIVE` means the holder already took ownership (not an error).
- **claim_token** `string`: Single-use claim token (shown once); `null` if the account is already claimed, or if a claim link issued earlier is still valid (see `claim_link_already_issued`).
- **claim_url** `string` (uri): Ready-to-use claim link for the account holder, built by BeeL for the current environment (no need to assemble `/reclamar?token=` yourself). `null` when `claim_token` is `null`.
- **claim_link_already_issued** (required) `boolean`: `true` when this call repeated the provision of an unclaimed account that already has a claim link still valid (pending and not expired). That link is **kept working**: nothing is issued or revoked, `claim_token` and `claim_url` come back `null`, and no email is re-sent even with `send_email: true` (there is no token in clear to send). The link you already handed out is the one the holder uses. To replace it on purpose, call `POST /v1/accounts/{account_id}/claim-tokens`, which revokes the previous one. `false` otherwise. (example: false)
- **company_id** `string` (uuid): The account's company id (a UUID) — send it in the `BeeL-Active-Company` header to operate on this account. Every account is born with one company: with a `tax_profile` it is ready to invoice; without one it has no NIF yet (the holder registers it on claim), and its issuing readiness reports `COMPANY_HAS_NO_NIF` until then. `null` only when resending an `external_ref` whose account now holds two or more companies.

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