# Recurring invoices

Schedule invoices that generate themselves — cadence, first occurrence, review drafts, automatic sending, how a series ends, and what happens when a generation fails.

A recurring invoice is a **template** plus a **schedule**. The template holds what every
invoice repeats (lines, recipient, series, payment); the schedule decides when the next one
is generated. Every generated invoice is a normal invoice: it takes the next number of the
template's series and goes through the same issuing rules as one you create by hand.

All operations live under `/v1/companies/{company_id}/recurring-invoices`.

## How the schedule works

### Cadence and day of the month

| Field | Meaning |
|---|---|
| `frequency` | `MONTHLY`, `QUARTERLY` or `YEARLY` — every 1, 3 or 12 months. Omitted, `MONTHLY` applies. |
| `day_of_month` | `1`–`31`. The day each invoice is generated. |
| `start_date` | When the subscription starts. |

A month that does not have `day_of_month` falls back to its **last day**: a template on `31`
generates on 28 February (29 in a leap year) and on 30 April. So `31` is how you ask for "the
last day of the month". The adjustment never sticks — each date is recalculated from the value
you sent, so the schedule does not drift (31 Jan → 28 Feb → 31 Mar).

### The first occurrence

The **cadence governs the step, not where the first invoice lands**. The first occurrence is
the first `day_of_month` on or after `start_date`, looked for one month at a time whatever the
cadence; from there the cadence takes over.

A `YEARLY` template starting 15 February with `day_of_month: 10` first invoices on **10 March**,
then every 10 March after that. It does not wait a year.

### Generation runs once a day, and never looks back

Due templates are generated in a daily run early in the morning, **Madrid time**. An invoice
is always issued with that day's date, so the system never back-fills:

- A `start_date` in the past is accepted and stored as sent (useful when migrating
  subscriptions), but the first generation is the next date of the template's calendar that
  is still ahead — today included. On a quarterly or yearly template that can be months away:
  a quarterly template on day 10 that started on 10 January 2025, created on 24 September
  2026, first generates on 10 October 2026. The periods already missed are **not** generated.
- Resuming a paused template whose next date has passed reschedules it to the first
  occurrence strictly after today. The periods missed while paused are not generated either.

`next_generation` on the template is the date the system will actually honour, or `null` when
there is none (the template is `COMPLETED`, or `PAUSED` with its date already behind it). An
`ACTIVE` template can show a date in the past: that is the occurrence the next run is about to
generate.

## Create a template

```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 2f1c7a52-8f0e-4c1b-9a4e-1d2b3c4d5e6f" \
  -d '{
    "name": "Monthly maintenance — Acme",
    "frequency": "MONTHLY",
    "day_of_month": 1,
    "start_date": "2026-10-01",
    "series_id": "{series_id}",
    "invoice_type": "STANDARD",
    "customer_id": "{customer_id}",
    "lines": [
      {
        "description": "Website maintenance",
        "quantity": 1,
        "unit_price": 150.00,
        "vat_rate": 21
      }
    ],
    "max_invoices": 12,
    "draft_in_advance": true,
    "send_automatically": true
  }'
```

Required: `name`, `day_of_month`, `start_date`, `series_id`, `invoice_type` (`STANDARD` or
`SIMPLIFIED`) and at least one line. Recurring lines use **flat** tax fields (`vat_rate`,
`irpf_rate`, `equivalence_surcharge_rate`…), not the `main_tax` object of an invoice line, and
each line carries exactly one of `unit_price`, `total_excluding_tax` or `total_including_tax`.

<Callout type="warn">
  **A template line does not inherit the company's default IRPF.** Unlike an invoice line, a
  template line sent without `irpf_rate` generates invoices with no withholding. If your
  invoices carry IRPF, send `irpf_rate` on every template line.
</Callout>

The response carries the template with its `next_generation`, `status: "ACTIVE"` and `amount`
— what the customer will be asked to pay on the next invoice (base + VAT + surcharge −
withholding + disbursements).

### Already have the invoice? Derive the template from it

If you have an invoice that already says what you want to repeat, derive the template from it
and describe only the recurrence. Lines, recipient, series and payment data are taken from the
source invoice, which is not modified.

```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices/derivations" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from_invoice_id": "{invoice_id}",
    "name": "Quarterly support — Acme",
    "frequency": "QUARTERLY",
    "day_of_month": 31,
    "start_date": "2026-10-01"
  }'
```

`from_invoice_id`, `name`, `day_of_month` and `start_date` are required. The cadence is never
copied from the source invoice — a one-off invoice has none — so `frequency` defaults to
`MONTHLY` here too.

## Review drafts and automatic sending

### `draft_in_advance`

| Value | What happens |
|---|---|
| `false` (default) | The invoice is generated and issued on the scheduled day. |
| `true` | A **draft** is created **5 days** before the scheduled day so you can review it, and it is issued on the scheduled day. |

Either way the invoice is issued on the scheduled day; the only difference is whether there is
a draft to look at first. The window is fixed at 5 days.

<Callout type="info">
  `preview_days` is **deprecated** and still accepted: any value above `0` means
  `draft_in_advance: true`, `0` means `false`. If you send both, `draft_in_advance` wins.
</Callout>

### `send_automatically`

With `send_automatically: true`, each generated invoice is emailed once issued, to the
recipients resolved as described in [Sending email](/guides/sending-email#recipients) — the
template's `email_configuration.recipients` first, then the customer's `email`.

A template with `send_automatically: true` and **no recipient that can be resolved** from either
source is rejected on write with `422` [`SIN_DESTINATARIO_RESOLUBLE`](/errors/SIN_DESTINATARIO_RESOLUBLE), instead of failing silently
every month. If the recipient loses its email address later, the invoice is still issued on
schedule — only the email is not sent.

Emails sent this way count against your [send quotas](/guides/sending-email).

## How a series ends

A recurrence ends in **exactly one** way:

| Mode | How |
|---|---|
| Open-ended | Neither `end_date` nor `max_invoices`. |
| On a date | `end_date`. |
| After N invoices | `max_invoices`, between `2` and `600` (`422` [`RECURRING_MAX_INVOICES_BELOW_MINIMUM`](/errors/RECURRING_MAX_INVOICES_BELOW_MINIMUM) / `422` [`RECURRING_MAX_INVOICES_ABOVE_MAXIMUM`](/errors/RECURRING_MAX_INVOICES_ABOVE_MAXIMUM) outside). |

Sending both `end_date` and `max_invoices` with a value is rejected with
[`RECURRING_END_MODE_CONFLICT`](/errors/RECURRING_END_MODE_CONFLICT). The conflict is judged on the **resulting** template, so a
`PATCH` that adds `max_invoices` to a template that already has an `end_date` is rejected too.
To switch modes, say both things in the same call:

```bash
curl -X PATCH "https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "max_invoices": 6, "end_date": null }'
```

`max_invoices` is the **total agreed**, not what is left: remaining is
`max_invoices − generated_invoices`. It counts invoices **generated**: a skip does not spend it,
a generate-now does. Below `2` you do not want a recurrence but a
[scheduled invoice](/invoices/setCompanyInvoiceSchedule).

When the last invoice is generated, or the next date falls past `end_date`, the template
becomes `COMPLETED` on its own.

## States

| `status` | Meaning |
|---|---|
| `ACTIVE` | Generating on schedule. |
| `PAUSED` | Not generating. `pause.reason` says why: `USER`, `DOWNGRADE` (the account lost the recurring-invoices feature) or `GENERATION_FAILURE`. |
| `COMPLETED` | Terminal. `completion.reason` says why: `USER`, `END_DATE_REACHED` or `MAX_INVOICES_REACHED`. Treat `completion.reason` as an open set. |

You move a template between states with
[`PUT …/status`](/recurring-invoices/setCompanyRecurringInvoiceStatus):

```bash
curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices/{recurring_invoice_id}/status" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "PAUSED" }'
```

- `PAUSED` stops generation and keeps the schedule.
- `ACTIVE` resumes. It keeps the next date if it has not fallen due yet (today included), and
  reschedules it only if it is already in the past. If nothing is left to generate, the template
  becomes `COMPLETED`.
- `COMPLETED` ends the template for good. From there, `ACTIVE` and `PAUSED` are rejected with
  `400` [`RECURRING_STATE_TRANSITION_INVALID`](/errors/RECURRING_STATE_TRANSITION_INVALID), and every write — including a `PATCH` that only
  touches notes — answers `409` [`RECURRING_ALREADY_ENDED`](/errors/RECURRING_ALREADY_ENDED). A completed template can still be
  deleted.

## When a generation fails

The daily run separates failures that waiting can fix from failures it cannot.

| Failure | What happens to the template |
|---|---|
| **Transient** (a timeout, the tax authority being down…) | Stays `ACTIVE`. `is_failing` becomes `true` from the first failure, a `FAILED` entry is added to the history, and the next day's run tries the same period again. `is_failing` clears after a successful generation. |
| **Permanent** (the company can no longer issue, a rejection that retrying will not change) | Moves to `PAUSED` with `pause.reason: "GENERATION_FAILURE"`. `pause.blocker` names what blocked it, when the cause is a readiness one. A `PAUSED` entry is added to the history, and the `recurring_invoice.paused` webhook is delivered. |

<Callout type="warn">
  **Resuming is rejected while the blocker is still in effect.** A template paused with a
  `pause.blocker` answers `422` [`EMISSION_NOT_READY`](/errors/EMISSION_NOT_READY) to `{"status": "ACTIVE"}` until that blocker
  is resolved. Fix the cause first — `pause.blocker` uses the same codes as the `blockers[]` of
  an [`EMISSION_NOT_READY`](/errors/EMISSION_NOT_READY) — then resume.
</Callout>

Subscribe to [`recurring_invoice.paused`](/webhook-events/onRecurringInvoicePaused) if your
billing depends on these invoices. It is delivered when a template stops **without anyone
asking** — a permanent failure or a plan downgrade — and not when a person pauses it. It is
also delivered in Test.

To find templates that need attention, filter the list by `pause_reason`:

```bash
curl "https://app.beel.es/api/v1/companies/{company_id}/recurring-invoices?pause_reason=GENERATION_FAILURE" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

A transient failure does not pause anything, so it does not show up in that filter: look for
`is_failing: true` on `ACTIVE` templates.

## Actions on a template

| Action | Endpoint | What it does |
|---|---|---|
| Skip | [`POST …/skip`](/recurring-invoices/skipCompanyRecurringInvoice) | Skips the next generation without issuing anything. The next date always lands after today. Only on `ACTIVE` templates. |
| Generate now | [`POST …/generate`](/recurring-invoices/generateCompanyRecurringInvoiceNow) | Generates the pending occurrence **now**. It brings the period forward, it does not add one: `next_generation` advances one step. Answers `201` with `invoice_id` and the new `next_generation` (`null` when that was the last invoice). Only on `ACTIVE` templates. |
| Next occurrence | [`GET …/next-occurrence`](/recurring-invoices/getCompanyRecurringInvoiceNextOccurrence) | The invoice the next generation would produce. Nothing is saved and no number is used, so `invoice_number` is `null`. |
| Stats | [`GET …/stats`](/recurring-invoices/getCompanyRecurringInvoiceStats) | Company-wide `active` and `stopped` blocks, each with `count`, `monthly_amount` and `uncounted_count`. |

Skip and generate-now on a template that is not `ACTIVE` answer `400` [`RECURRING_NOT_ACTIVE`](/errors/RECURRING_NOT_ACTIVE).

Generating manually, skipping and letting the schedule run each consume exactly one occurrence,
however you mix them. Generate-now is a fiscal act: the invoice takes a number and, if the
template says so, is issued and sent.

<Callout type="info">
  **Need an extra invoice outside the calendar?** Don't use generate-now — it consumes the
  next period. Create a normal invoice, or derive one from an invoice the template already
  generated with [`POST …/invoices/derivations`](/invoices/createCompanyInvoiceDerivation).
</Callout>

## History

[`GET …/history`](/recurring-invoices/getCompanyRecurringInvoiceHistory) returns every entry
of the schedule, newest first and paginated (20 per page by default). It tells you not only
which invoices were generated but also why a period has **no** invoice.

| Field | Meaning |
|---|---|
| `type` | `GENERATED`, `FAILED`, `SKIPPED` or `PAUSED` (an automatic pause). Only `GENERATED` produces an invoice. |
| `invoice_id` | The generated invoice, or `null` — always `null` for `FAILED`, `SKIPPED` and `PAUSED`, and also when the invoice is no longer reachable (for example a draft that was deleted). |
| `origin` | Who produced the entry: `MANUAL` (someone asked) or `UNATTENDED` (the daily run). Generations carry it, and so does a `FAILED` entry, always `UNATTENDED`: only the daily run records failures. |
| `reason` | Human-readable explanation of a `FAILED` or `PAUSED` entry, in the caller's language. Presentation text: branch on `type`, not on this. |
| `requested_by` / `requested_by_name` | Who skipped the slot. Only on `SKIPPED` entries. |
| `scheduled_date` | The calendar slot the entry refers to. |
| `generated_at` | When the entry was written: the moment of the generation, the skip, the failure or the pause. On a `FAILED` entry it is the time of the failed attempt, not of an invoice. |
| `invoice_number` / `total` / `status` | The generated invoice's current number, total and status. Only on `GENERATED` entries whose invoice is still reachable. |

Fields that do not apply to an entry may be missing instead of `null`: read them as optional.

A generate-now and a skip, as the history returns them:

```json
[
  {
    "id": "d9f2661c-3028-4a06-b948-45598da919bc",
    "type": "GENERATED",
    "invoice_id": "4dfe3e8e-7977-4b39-90d4-6eac1efdcf8d",
    "origin": "MANUAL",
    "reason": null,
    "requested_by_name": null,
    "generated_at": "2026-09-24T07:38:28.488041Z",
    "scheduled_date": "2026-09-24",
    "invoice_number": "VN/000153",
    "total": 121,
    "status": "ISSUED"
  },
  {
    "id": "b05246e2-b215-4f0e-940b-1cfe5dedc1e2",
    "type": "SKIPPED",
    "invoice_id": null,
    "reason": null,
    "requested_by": "395176a9-438f-4077-96aa-4ebee6185f0e",
    "requested_by_name": "…",
    "generated_at": "2026-09-24T07:37:44.464148Z",
    "scheduled_date": "2026-10-01"
  }
]
```

A `FAILED` entry has `invoice_id: null`, `origin: "UNATTENDED"` and a `reason`.

<Callout type="warn">
  **Handle `invoice_id: null`.** Code that assumed every history entry points to an invoice
  breaks on `FAILED`, `SKIPPED` and `PAUSED` entries.
</Callout>

## Gotchas

- **Quarterly and yearly amounts are not normalised in the stats.** `monthly_amount` adds up
  the next invoice of each template as is, so a quarterly template contributes its full
  quarterly amount. The two blocks (`active` and `stopped`) are never meant to be added
  together.
- **Generate-now before `start_date`** is rejected with `422` [`RECURRING_NOT_STARTED`](/errors/RECURRING_NOT_STARTED): there is no
  occurrence to bring forward yet. Create a normal invoice instead.
- **A cadence and an `end_date` that leave no occurrence** — a yearly template covering a
  calendar year already under way — are rejected with [`RECURRING_NO_OCCURRENCE_IN_WINDOW`](/errors/RECURRING_NO_OCCURRENCE_IN_WINDOW).
- **`payment_iban`, `payment_swift` and `payment_term_days`** are only accepted together with a
  `payment_method` that is not `null` or `NONE` (`422` [`PAYMENT_DETAILS_REQUIRE_METHOD`](/errors/PAYMENT_DETAILS_REQUIRE_METHOD)).
- **`PATCH` replaces `lines` as a whole**: send `lines` and the template keeps only the lines
  you sent. A `PATCH` without `lines` keeps them. The payment fields travel together:
  `payment_iban`, `payment_swift` or `payment_term_days` without `payment_method` in the same
  call answers `422` [`PAYMENT_DETAILS_REQUIRE_METHOD`](/errors/PAYMENT_DETAILS_REQUIRE_METHOD), even if the template already has a method.
  A `PATCH` that touches none of them keeps the payment data.
- **Changing `invoice_type` on a `PATCH`** is judged on the resulting template, with the rules
  of the new type: the series has to accept it (`422` [`SERIES_INCOMPATIBLE_DOC_TYPE`](/errors/SERIES_INCOMPATIBLE_DOC_TYPE)),
  and a `SIMPLIFIED` template cannot keep an identified recipient
  (`422` [`SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT`](/errors/SIMPLIFIED_INVOICE_FORBIDS_IDENTIFIED_RECIPIENT)) nor lines a simplified
  invoice does not admit. Send the new `series_id`, and `customer_id: null` when moving to
  `SIMPLIFIED`, in the same call. Without `invoice_type`, the type is kept.
- **VeriFactu is not a template setting.** Whether each generated invoice is registered with
  the AEAT is decided when that invoice is issued, from the company's regime at that moment.

## Related

<Related>

- [List recurring invoices](/recurring-invoices/listCompanyRecurringInvoices)
- [Create a recurring invoice](/recurring-invoices/createCompanyRecurringInvoice)
- [Derive a recurring invoice from an invoice](/recurring-invoices/createCompanyRecurringInvoiceDerivation)
- [Get a recurring invoice](/recurring-invoices/getCompanyRecurringInvoice)
- [Update a recurring invoice](/recurring-invoices/patchCompanyRecurringInvoice)
- [Delete a recurring invoice](/recurring-invoices/deleteCompanyRecurringInvoice)
- [Set the status](/recurring-invoices/setCompanyRecurringInvoiceStatus)
- [Skip the next generation](/recurring-invoices/skipCompanyRecurringInvoice)
- [Generate now](/recurring-invoices/generateCompanyRecurringInvoiceNow)
- [Next occurrence](/recurring-invoices/getCompanyRecurringInvoiceNextOccurrence)
- [Generation history](/recurring-invoices/getCompanyRecurringInvoiceHistory)
- [Stats](/recurring-invoices/getCompanyRecurringInvoiceStats)
- [`recurring_invoice.paused` webhook](/webhook-events/onRecurringInvoicePaused)

</Related>

---

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