# Get the generation history of a recurring invoice API Reference

Returns **every entry** in this schedule's history, newest first: the invoices it generated
and, just as important, the periods it did **not** — a failed unattended run, a slot a user
skipped, or the schedule being paused. Each entry carries its `type`, the `origin` of who
asked for it, a translated `reason` when there is one, and `requested_by` for a skip. That is
what lets you answer "why is there no invoice for March?" without reading logs.

Only `GENERATED` entries have an `invoice_id`; for the rest it is `null`.

**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get
the 20 most recent entries, not the whole history — which grows with every cycle the
template runs. Read `data.pagination` to walk the rest.

The deprecated flat alias `GET /v1/recurring-invoices/{recurring_invoice_id}/history` does
**not** paginate and returns **only generated invoices**: it is frozen as it shipped until
its `Sunset` date. Only this route returns every entry type.


## GET /v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/history

**Get the generation history of a recurring invoice**

Returns **every entry** in this schedule's history, newest first: the invoices it generated
and, just as important, the periods it did **not** — a failed unattended run, a slot a user
skipped, or the schedule being paused. Each entry carries its `type`, the `origin` of who
asked for it, a translated `reason` when there is one, and `requested_by` for a skip. That is
what lets you answer "why is there no invoice for March?" without reading logs.

Only `GENERATED` entries have an `invoice_id`; for the rest it is `null`.

**Paginated** with the usual `page`/`limit`, and the usual defaults: without them you get
the 20 most recent entries, not the whole history — which grows with every cycle the
template runs. Read `data.pagination` to walk the rest.

The deprecated flat alias `GET /v1/recurring-invoices/{recurring_invoice_id}/history` does
**not** paginate and returns **only generated invoices**: it is frozen as it shipped until
its `Sunset` date. Only this route returns every entry type.

### 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.
- **recurring_invoice_id** (required) in path `string`: Unique identifier (UUID) of the recurring invoice template, as returned when it is created or listed. A template of another company answers `404` with `RECURRING_NOT_FOUND`, exactly like one that does not exist.
- **page** (optional) in query `integer` (default: 1): Page number, starting at 1. The response echoes it back as `pagination.current_page`.
- **limit** (optional) in query `integer` (default: 20): How many items to return per page. The response echoes it back as `pagination.items_per_page`.

### Responses

#### 200: Generation history retrieved successfully

**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** `object`: No description
  - **history** (required) `array[GenerationHistoryResponse]`: No description
  - **pagination** (required) `Pagination`

#### 400: The request target could not be read as declared, so nothing was looked up. Three causes,
all named in `error.details`: a path or query parameter whose value does not parse as its
type (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty
element in a list such as `status=ISSUED,`), a required parameter that was not sent
(`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not
(`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is
not an error: it is the same as omitting it.

The fault is in the target, not in the content — which is what `422` is defined over.


**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": "VALIDATION_ERROR",
    "message": "The parameter 'invoice_id' has an invalid type. Expected: UUID.",
    "details": {
      "field": "invoice_id",
      "invalid_value": "deliveries",
      "expected_format": "UUID"
    }
  },
  "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: Your account does not own or manage this company, or does not hold the required
access over it. A NIF that does not exist answers the same way.
That case answers `error.code` `ACTIVE_COMPANY_NOT_ACCESSIBLE`.


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

#### 404: The resource addressed by the path does not exist, or is not one this credential can see —
the two are answered identically, so existence is never disclosed. `error.code` names the
kind of resource that was missing, so a client can tell which of several ids in a path
failed to resolve:

- `INVOICE_NOT_FOUND` — the `{invoice_id}` (invoices, proformas and their sub-resources).
- `RECURRING_NOT_FOUND` — the `{recurring_invoice_id}`.
- `CLIENT_NOT_FOUND` — the `{customer_id}`.
- `INVOICE_CLIENT_NOT_FOUND` — the `customer_id` referenced by an invoice body does not
  resolve to a customer of the issuing company.
- `PRODUCT_NOT_FOUND` — the `{product_id}`.
- `SERIES_NOT_FOUND` — the `{series_id}`.
- `WEBHOOK_SUBSCRIPTION_NOT_FOUND`, `DELIVERY_LOG_NOT_FOUND` — the `{webhook_id}` and the
  `{delivery_id}`.
- `PROFILE_NOT_FOUND` — the credential's account has no company profile yet: its holder
  has not completed onboarding, so there is no issuer to act as.
- `NOT_FOUND` — the generic fallback, for the few resources that carry no code of their own.

Some resources declare a more specific `404` response of their own (`CONNECTION_NOT_FOUND`,
`MEMBER_NOT_FOUND`, `GRANT_NOT_FOUND`, `INVITATION_NOT_FOUND`, `REQUEST_LOG_NOT_FOUND`); it
is documented on the operation.

A `404` with `ENDPOINT_NOT_FOUND` is a different answer: the **path itself** does not exist
in this API (a typo in the route, or a resource that was never here). It says nothing about
any resource. When the path exists but not with that method, the answer is `405`, not `404`.


**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": "INVOICE_NOT_FOUND",
    "message": "Invoice not found"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 422: A parameter parsed as its declared type but its value is not one the operation admits:
a `limit` outside its range (`limit=0`, `limit=101`), a `page` under 1, a `cursor` that is
not one this API issued, more keys than the `metadata` filter accepts, a value outside the
supported set. The query was rejected whole and nothing was read.

Where a `400` says the target could not be understood, this says it was understood and
refused. Fix the value — retrying the same query answers the same way.


**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": "VALIDATION_ERROR",
    "message": "Validation constraint violation.",
    "details": {
      "limit": "must be greater than or equal to 1"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

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


**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 `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.


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

## GenerationHistoryResponse

- **id** `string` (uuid): No description
- **type** `RecurringInvoiceHistoryEntryType`: What happened in one scheduled slot of the schedule. Only `GENERATED` consumes the slot and produces an invoice. `FAILED`, `SKIPPED` and `PAUSED` are recorded to explain why a period has no invoice, and the slot stays free: the next run may still fill it.
- **invoice_id** `string` (uuid): Invoice this entry generated, `null` for entries that generated none — `FAILED`, `SKIPPED` and `PAUSED` never produce one. It is also `null` when the invoice is no longer reachable, under the same conditions as `invoice_number`.
- **origin**: Who triggered the generation: `MANUAL` a person did, `UNATTENDED` the nightly sweep. Absent for entries that are not a generation, and for generations recorded before this was stored.
- **reason** `string`: Human-readable explanation of a `FAILED` or `PAUSED` entry, already translated to the caller's language. `null` when the entry carries no reason (a generation or a skip). It is presentation text, not a stable code: do not branch on it. The stable fact is `type`.
- **requested_by** `string` (uuid): User who asked to skip the slot. Only present for `SKIPPED` entries.
- **requested_by_name** `string`: Human-readable identity of `requested_by`: the user's name when the backend has one, their email otherwise. Never the raw UUID — that stays in `requested_by`. Only present for `SKIPPED` entries; `null` when there is no `requested_by` (a generation, failure or pause).
- **generated_at** `string` (date-time): No description
- **scheduled_date** `string` (date): No description
- **invoice_number** `string`: Number of the invoice this entry generated, `null` when that invoice is no longer reachable — it was deleted while still a draft, or it falls outside the caller's scope. A draft that has not been issued yet has no number either, and reads `null` too. The entry itself always survives: an occurrence that happened is not unsaid by the invoice it produced disappearing.
- **total** `number`: Total amount of the generated invoice, `null` under the same conditions as `invoice_number`. Resolved on read, so it always reflects the invoice as it is now.
- **status**: Current status of the generated invoice, `null` under the same conditions as `invoice_number`. Resolved on read and never stored on the history row: an entry generated months ago reports what its invoice is TODAY, not what it was when born.

## Pagination

- **current_page** (required) `integer`: No description (example: 1)
- **total_pages** (required) `integer`: How many pages the query has. An empty collection has **one** page — the first one, empty — so the smallest value a canonical route returns is `1`. The deprecated flat aliases (`/v1/invoices`, `/v1/customers`, …) answer `0` for that same empty collection, and `has_next`/`has_previous` follow suit. They were left as they were on purpose: a client that reads `total_pages == 0` as "no results" would break if it changed under it. Do not compare the two families of routes field by field; read `total_items` if what you want to know is whether anything came back. (example: 5)
- **total_items** (required) `integer`: No description (example: 87)
- **items_per_page** (required) `integer`: No description (example: 20)
- **has_next** `boolean`: No description
- **has_previous** `boolean`: No description

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

## RecurringInvoiceHistoryEntryType

What happened in one scheduled slot of the schedule.

Only `GENERATED` consumes the slot and produces an invoice. `FAILED`, `SKIPPED` and `PAUSED` are
recorded to explain why a period has no invoice, and the slot stays free: the next run may still
fill it.

Type: `string` — one of: GENERATED, FAILED, SKIPPED, PAUSED


---

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