# Get the fiscal summary of a company API Reference

Returns the VAT and IRPF summary of the invoices issued under this company over the requested period, together with the annual IRPF projection and its progressive bracket breakdown.
`start_date` and `end_date` go together: send both, or neither. Omitting both defaults to the current month; sending only one answers `400`, because a period you did not ask for is worse than an error. The range may not exceed 365 days, and every fault names itself in `details.reason`.

## GET /v1/companies/{company_id}/fiscal-summary

**Get the fiscal summary of a company**

Returns the VAT and IRPF summary of the invoices issued under this company over the requested period, together with the annual IRPF projection and its progressive bracket breakdown.
`start_date` and `end_date` go together: send both, or neither. Omitting both defaults to the current month; sending only one answers `400`, because a period you did not ask for is worse than an error. The range may not exceed 365 days, and every fault names itself in `details.reason`.

### 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.
- **start_date** (optional) in query `string`: Period start date (inclusive), as `YYYY-MM-DD`. Goes together with `end_date`: supply both or neither. Omitting both defaults to the current month; supplying only one is rejected with `400` (`PERIOD_INCOMPLETE`).
- **end_date** (optional) in query `string`: Period end date (inclusive), as `YYYY-MM-DD`. Goes together with `start_date`: supply both or neither. Omitting both defaults to the current month; supplying only one is rejected with `400` (`PERIOD_INCOMPLETE`).

### Responses

#### 200: The fiscal summary of the company for the requested period.

**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** (required) `FiscalSummaryResponse`: Comprehensive fiscal summary for a period

#### 400: The request target is malformed: a date that is not `YYYY-MM-DD`, or a range that is
not a period. A range fault names itself in `details.reason`:
- `PERIOD_INVERTED` — `start_date` is after `end_date`
- `PERIOD_TOO_LONG` — the range exceeds `details.max_days` (365)
- `PERIOD_INCOMPLETE` — only one of `start_date` / `end_date` was supplied

`details` also echoes whichever of `start_date` and `end_date` was received. These are query
parameters, so the fault is in the request target and the status is `400`, not `422`.


**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")

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

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

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

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

## FiscalSummaryResponse

Comprehensive fiscal summary for a period

- **queried_period** (required) `QueriedPeriod`: Information about the queried period
- **total_taxable_base** (required) `number` (double): Total taxable base for the period (example: 15000)
- **total_vat** (required) `number` (double): Total indirect tax for the period (IVA, IGIC, IPSI and other rates), not only VAT. See tax_breakdown for the split by tax type. (example: 3150)
- **tax_breakdown** `array[TaxBreakdownItem]`: Tax breakdown by type (IVA, IGIC, IPSI) and rate. Allows a single invoice to have multiple tax types.
- **vat_breakdown_by_rate** `object`: DEPRECATED: Use tax_breakdown instead. Indirect tax breakdown by rate (rate -> amount). It aggregates EVERY indirect tax (IVA, IGIC, IPSI and other rates) by percentage alone, so two different tax types at the same rate collapse into a single entry and cannot be told apart. The rate key is always rendered with three decimals ("21.000", "0.000"). `tax_breakdown` is the replacement: it keeps `tax_type` and `percentage` separate. (example: {"21.000":3150})
- **surcharge_breakdown** `array[SurchargeBreakdownItem]`: Equivalence surcharge breakdown by rate, aggregated over the period. Same shape as `surcharge_breakdown` in the invoice detail. Empty (or absent) when no invoice in the period carries an equivalence surcharge.
- **total_equivalence_surcharge** `number` (double): Total equivalence surcharge for the period. It is NOT part of `total_vat`: the surcharge is settled by the retailer, not by the issuer, and `total_vat` only carries the main indirect tax. (example: 52)
- **total_irpf_withheld** (required) `number` (double): Total IRPF withheld in the period (example: 750)
- **period_base** (required) `number` (double): Taxable base for the period (example: 15000)
- **projected_annual_base** (required) `number` (double): Projected annual taxable base (example: 60000)
- **estimated_annual_irpf** (required) `number` (double): Estimated annual IRPF based on projected income (example: 14582.5)
- **pending_annual_irpf** (required) `number` (double): Pending annual IRPF (estimated - retained) (example: 11546.17)
- **bracket_details** (required) `array[IrpfBracket]`: IRPF breakdown by tax brackets
- **invoices** `array[InvoiceFiscalData]`: List of invoices included in the calculation
- **total_invoices** (required) `integer`: Total number of invoices (example: 12)

## 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"})

## QueriedPeriod

Information about the queried period

- **start_date** (required) `string` (date): Period start date (example: "2025-01-01")
- **end_date** (required) `string` (date): Period end date (example: "2025-03-31")
- **days_included** (required) `integer`: Number of days included in the period (example: 90)

## TaxBreakdownItem

Tax breakdown by type (IVA, IGIC, IPSI) and rate

- **tax_type** (required) `string`: Tax type: IVA, IGIC, IPSI, OTHER — one of: IVA, IGIC, IPSI, OTHER (example: "IVA")
- **percentage** (required) `number` (double): Tax rate percentage (example: 21)
- **taxable_base** (required) `number` (double): Taxable base for this tax type/rate (example: 10000)
- **tax_amount** (required) `number` (double): Tax amount (base * rate / 100) (example: 2100)
- **regime_key** `string`: VeriFactu regime key of the rows grouped here. Rows are grouped by (tax type, rate, regime key), so an invoice mixing general-regime and OSS lines at the same rate yields TWO rows at `percentage: 21` that only this field tells apart (`01` vs `17`). Index by `(tax_type, percentage, regime_key)`, never by `(tax_type, percentage)` alone. (example: "01")

## SurchargeBreakdownItem

Equivalence surcharge breakdown by rate. Same shape as `surcharge_breakdown` in the invoice detail.

- **type** (required) `number` (double): Surcharge rate percentage (example: 5.2)
- **base** (required) `number` (double): Taxable base subject to this surcharge rate (example: 1000)
- **amount** (required) `number` (double): Surcharge amount (base * rate / 100) (example: 52)

## IrpfBracket

IRPF tax bracket with calculated values

- **base_from** (required) `number` (double): Lower bound of taxable base (example: 0)
- **base_to** `number` (double): Upper bound of taxable base (null for unlimited) (example: 12450)
- **rate_percentage** (required) `number` (double): Tax rate percentage for this bracket (example: 19)
- **applicable_base** (required) `number` (double): Portion of income falling in this bracket (example: 12450)
- **amount** (required) `number` (double): Tax amount for this bracket (example: 2365.5)

## InvoiceFiscalData

Invoice fiscal data for summary

- **id** (required) `string` (uuid): Invoice ID
- **invoice_number** (required) `string`: Invoice number (example: "F2025/001")
- **issue_date** (required) `string` (date): Issue date (example: "2025-01-15")
- **customer_name** `string`: Customer name (example: "Empresa SL")
- **taxable_base** (required) `number` (double): Taxable base (example: 1000)
- **total_vat** (required) `number` (double): Total indirect tax amount (IVA, IGIC, IPSI and other rates), not only VAT. See tax_breakdown for the split by tax type. (example: 210)
- **total_irpf** (required) `number` (double): IRPF withholding amount (example: 150)
- **invoice_total** (required) `number` (double): Total invoice amount (example: 1060)


---

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