Create a recurring invoice
Scopeinvoices:writeCreates a recurring invoice template under this company: the invoice data it repeats (lines, recipient, series, payment) plus the recurrence that drives it.
- Cadence:
frequencyis how often it generates — every 1 (MONTHLY), 3 (QUARTERLY) or 12 (YEARLY) months — onday_of_month, fromstart_dateuntilend_dateif one is given. Omitted,MONTHLYapplies. - The cadence governs the step, not the first invoice: the first occurrence is the
first
day_of_monthon or afterstart_date, found one month at a time whatever the cadence; the cadence takes over from there. AYEARLYtemplate starting 15 February withday_of_month10 first invoices on 10 March, then every 10 March after that — it does not wait a year. start_datein the past: accepted and stored as sent, but it never anchors generation backwards.next_generationbecomes the first date of the template's own calendar on or after today (today included) — the grid ofday_of_monthdates anchored atstart_date, one everyfrequency— so on a quarterly or yearly template it can land months from now, not this month. The missed periods are not generated. If today's period was already invoiced in the system you are migrating from, send astart_dateof tomorrow (or of the next period) so it is not invoiced twice.draft_in_advance: whether the invoice is created as a draft for review before it is emitted. The window is fixed at 5 days, and both options emit on the scheduled day. Generating manually a period whose scheduled date is today or earlier emits it on the spot. Omitted, no review draft is prepared. It supersedes the deprecatedpreview_days.- VeriFactu: the template does not carry it. Whether each generated invoice is registered with AEAT is decided when that invoice is issued, against the company's regime at that moment.
Keys are prefixed beel_sk_, and each one carries the scopes it was created with: a key
short of the scope an operation needs is answered 403. The scope an operation requires
is shown next to its title, and the full catalogue lives in the Scopes reference.
Keys are created from the BeeL dashboard. They are secret credentials: do not share them or commit them to source control.
In: header
Path Parameters
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.
uuidHeader Parameters
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. |
^[a-zA-Z0-9_-]+$length <= 255^\S.*$1 <= length <= 255Generation cadence: MONTHLY every month, QUARTERLY every 3 months, YEARLY every 12 months. Omitted, MONTHLY applies.
It governs the step from the first invoice onwards, not where that first one lands: the
first occurrence is the first day_of_month on or after start_date, found one month at
a time whatever the cadence. 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.
"MONTHLY" | "QUARTERLY" | "YEARLY"Day of the month the invoices are issued. A month that does not have that day falls
back to its last day: a template on 31 issues 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 it generates
in — there is no separate flag for it, and the accepted range stays 1–31.
The adjustment does not stick and the schedule does not drift: every generation is
recalculated from the day_of_month you sent, never from the date it was adjusted
to (31 Jan → 28 Feb → 31 Mar).
1 <= value <= 31Date the subscription started. A past date is accepted and stored as sent — useful
when migrating subscriptions from another system — but it never anchors generation
in the past. next_generation becomes the first date of the template's own calendar
on or after today (today included): the grid of day_of_month dates anchored at
start_date, one every frequency. On a quarterly or yearly template that can be
months from now, not this month. Invoices are never back-dated, so the missed periods
are not generated. If today's period was already invoiced in the system you are
migrating from, send a start_date of tomorrow (or of the next period) so it is not
invoiced twice.
dateWhether the template prepares a draft for review before emitting. The window is fixed at 5 days and both options emit on the scheduled day. Omitted, the template is created without the review draft.
Deprecated. Superseded by draft_in_advance, because the review window is no longer
a number you pick: it is fixed at 5 days. Still accepted so nothing breaks — any value
greater than 0 means the same as draft_in_advance: true, and 0 the same as false.
When both are sent, draft_in_advance wins. It will be removed in a future version.
0 <= value <= 30dateuuidType of the invoices a recurring template generates: STANDARD for an identified recipient,
SIMPLIFIED for a recipient that is not identified.
"STANDARD" | "SIMPLIFIED"Total number of invoices this template will generate before ending on its own, between
2 and 600. Leave it out (or send null) for a recurrence that is not capped by number.
A recurrence ends in ONE way: open-ended, on end_date, or after max_invoices. Sending
both end_date and max_invoices with a value is rejected with RECURRING_END_MODE_CONFLICT,
and so is a max_invoices that lands on a template that already has an end_date: the
conflict is judged on the RESULTING state, not on the body. To switch modes, say both things
in the same call — the new field with a value and the old one as null. Nothing is cleared
silently.
The cap counts invoices GENERATED, not calendar turns: a skip does not spend it. The range
is not enforced by this schema on purpose, so the rejection carries its own code and its own
message: below 2 you do not want a recurrence but a scheduled invoice
(PUT /v1/companies/{company_id}/invoices/{invoice_id}/schedule), which also issues it on an exact date.
uuid1 <= items"NONE" | "BANK_TRANSFER" | "CARD" | "CASH" | "CHECK" | "DIRECT_DEBIT" | "BIZUM" | "OTHER"falseResponse Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://app.beel.es/api/v1/companies/497f6eca-6276-4993-bfeb-53cbbbba6f08/recurring-invoices" \ -H "Content-Type: application/json" \ -d '{ "name": "Mantenimiento web mensual", "frequency": "MONTHLY", "day_of_month": 1, "start_date": "2026-10-01", "end_date": "2027-09-30", "series_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "invoice_type": "STANDARD", "customer_id": "123e4567-e89b-12d3-a456-426614174000", "lines": [ { "description": "Mantenimiento mensual de la web corporativa", "quantity": 1, "unit": "mes", "unit_price": 450, "tax_type": "IVA", "vat_rate": 21, "regime_key": "01", "irpf_rate": 15 } ], "payment_method": "BANK_TRANSFER", "payment_iban": "ES9121000418450200051332", "payment_term_days": 15, "send_automatically": true, "email_configuration": { "recipients": [ "facturas@example.com" ], "subject": "Factura mensual de mantenimiento", "message": "Adjuntamos la factura de este mes." } }'{
"success": true,
"data": {
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"name": "string",
"frequency": "MONTHLY",
"day_of_month": 0,
"start_date": "2019-08-24",
"end_date": "2019-08-24",
"next_generation": "2019-08-24",
"draft_in_advance": true,
"status": "ACTIVE",
"is_failing": false,
"pause": {
"reason": "GENERATION_FAILURE",
"since": "2019-08-24T14:15:22Z",
"blocker": "NIF_NOT_REGISTERED"
},
"completion": {
"reason": "USER",
"at": "2019-08-24T14:15:22Z"
},
"series_id": "0f6582ac-1e7e-41dc-a63a-24fccd074d0c",
"series_code": "string",
"invoice_type": "STANDARD",
"customer_id": "160c0c4b-9966-4dc1-a916-8407eb10d74e",
"recipient_fiscal_name": "string",
"recipient_nif": "string",
"recipient_alternative_id": {
"type": "NIF_IVA",
"number": "string",
"country_code": "st"
},
"lines": [
{
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
"order": 0,
"description": "string",
"quantity": 0,
"unit": "string",
"unit_price": 0,
"pricing_mode": "UNIT_PRICE",
"total_excluding_tax": 1,
"total_including_tax": 100,
"discount_percentage": 0,
"tax_type": "string",
"vat_rate": 0,
"regime_key": "string",
"equivalence_surcharge_rate": 0,
"irpf_rate": 0,
"exemption_reason": "EXENTA_ART_20",
"exemption_reason_text": "string"
}
],
"amount": 106,
"payment_method": "string",
"payment_iban": "string",
"payment_swift": "string",
"payment_term_days": 0,
"notes": "string",
"send_automatically": true,
"email_configuration": {
"recipients": [
"string"
],
"cc": [
"string"
],
"subject": "string",
"message": "string"
},
"generated_invoices": 0,
"max_invoices": 6,
"last_generated_at": "2019-08-24T14:15:22Z",
"last_generated_scheduled_date": "2026-05-01",
"source_invoice_id": "0c7d5f47-8759-4ef4-82d5-705413539f77",
"created_at": "2019-08-24T14:15:22Z",
"updated_at": "2019-08-24T14:15:22Z"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "INVALID_JSON_FORMAT",
"message": "The field 'due_date' has an invalid date format: '2026-03-04fds'. Expected format: YYYY-MM-DD.",
"details": {
"field": "due_date",
"invalid_value": "2026-03-04fds",
"expected_format": "YYYY-MM-DD"
}
},
"meta": {
"timestamp": "2026-03-05T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication is required to access this resource"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}{
"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"
}
}{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"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"
}
}Get recurring invoice stats GET
How many recurring schedules this company has alive, what they add up to per month, and how much invoicing is stopped because the unattended generation broke. **It is the whole company, and it takes no filters.** It is an anchor, not a summary of whatever the list is showing: narrowing the list by customer or by status does not move these figures. Pagination does not apply either — the numbers cover every schedule of the company, not a page of them. **The two amounts are never added together.** `active.monthly_amount` is a forecast of what is going to be invoiced; `stopped.monthly_amount` is invoicing that should be happening and is not. There is deliberately no grand total in the response. The per-schedule figures behind them are the same ones `GET /v1/companies/{company_id}/recurring-invoices` publishes as `amount`, so the rows and this header cannot drift: adding the `amount` of every active row by hand gives `active.monthly_amount` exactly, to the cent. Filters of the list are **rejected**, not ignored: sending `status`, `customer_id` or any other unknown parameter answers `400` naming it. Asking for a filtered header and getting whole-company figures back with a `200` would be worse than being told no.
Derive a recurring invoice from an existing invoice POST
Creates a recurring invoice template of this company taking its lines, recipient, series and payment data from an existing invoice, so only the recurrence has to be described. - **`from_invoice_id`:** the source invoice. It must belong to the company in the path, and one you cannot reach is reported the same way as one that does not exist. It is not modified by this call. - **Recurrence:** `name`, `day_of_month` and `start_date` are required; `end_date` and `frequency` are optional. The cadence is not taken from the source invoice — a one-off invoice has none to copy — so it is described here like any other recurrence field: every 1 (`MONTHLY`), 3 (`QUARTERLY`) or 12 (`YEARLY`) months, `MONTHLY` when omitted. It governs the step from the first invoice onwards, not where that first one lands. - **VeriFactu:** not inherited from the source invoice. Each generated invoice is registered with AEAT, or not, according to the company's regime when it is issued.