# Facturas de venta recurrentes

> Una factura recurrente es una plantilla de factura programada: cuando llega el momento configurado, FacturaDirecta genera automáticamente la factura real a partir de la plantilla.


Una factura recurrente es una **plantilla de factura programada**: cuando
llega el momento configurado, FacturaDirecta genera automáticamente la
factura real a partir de la plantilla. Útil para suscripciones,
mantenimientos, alquileres, retainers y cualquier facturación periódica
con importes y conceptos estables.

> En los ejemplos de esta página:
>
> - Los **UUIDs** (`aut_…`, `con_…`, `upl_…`) son ilustrativos. Cada
>   empresa tiene los suyos; sustitúyelos por los identificadores reales
>   que devuelve la API.
> - El **ID de impuesto** `S_IVA_21` es el del catálogo por defecto
>   para IVA 21% en ventas. Recupera el catálogo real con
>   `GET /{companyId}/settings/taxes/sales`. Ver
>   [Impuestos](https://www.facturadirecta.com/desarrolladores/api/guias/taxes/).

## Conceptualmente: una automatización con `action: createDocument`

Una factura recurrente es **técnicamente una automatización**
(`content.type: "automation"`). Tiene un **trigger** (cuándo se dispara)
y una **action** (qué hace al dispararse, en este caso crear una factura).
El prefijo del identificador es `aut_<uuid>` (de "automation").

La API pública expone hoy un único uso de las automatizaciones: facturas
de venta recurrentes (`action.type: "createDocument"`,
`action.template.type: "invoice"`). El recurso puede extenderse a otras
recurrencias en el futuro.

## Doble scope

A diferencia del resto de recursos, cada operación de recurring requiere
**dos scopes simultáneamente**:

- Las operaciones de **lectura** requieren `recurring:read` **y**
  `invoices:read`.
- Las operaciones de **escritura** requieren `recurring:write` **y**
  `invoices:write`.

El motivo: la recurrente toca dos dominios — la programación (recurring)
y la factura que genera (invoices). Quien gestiona recurrentes debe
poder leer/escribir ambos.

## Estructura

- `content.type` — siempre `"automation"`.
- `content.uuid` — identificador inmutable, prefijo `aut_`.
- `content.main`:
  - `title` — nombre descriptivo de la recurrente.
  - `enabled` — `true`/`false`. Si `false`, las próximas ejecuciones
    programadas se omiten sin perderse en el calendario.
  - `trigger` — cuándo se dispara (ver [Trigger y rrule](#trigger-y-rrule)).
  - `action` — qué hace al dispararse (ver [Action: plantilla de factura](#action-plantilla-de-factura)).
- `content.attachments` — adjuntos vinculados (ver [Adjuntos](#adjuntos)).

### Trigger y `rrule`

El trigger sigue el estándar RFC 5545 (iCalendar) en una versión
simplificada. Forma:

```json
"trigger": {
  "type": "recurring",
  "rrule": {
    "freq": "monthly",
    "interval": 1,
    "dtstart": "2026-06-01T06:00:00.000Z",
    "until": "2027-06-01T06:00:00.000Z",
    "bymonthday": 1
  },
  "daysAdvance": 0
}
```

Campos de `rrule`:

- **`freq`** (obligatorio) — frecuencia base. Valores: `daily`, `weekly`,
  `monthly`, `yearly`.
- **`interval`** (obligatorio, entero ≥1) — cada cuántas unidades de
  `freq`. Ejemplos: `freq: "monthly", interval: 1` (mensual);
  `freq: "monthly", interval: 3` (trimestral); `freq: "weekly", interval: 2` (cada dos semanas).
- **`dtstart`** (obligatorio) — fecha-hora de inicio en ISO 8601.
  Solo se evalúan ejecuciones a partir de esta fecha.
- **`until`** o **`count`** (opcional, mutuamente excluyentes) — fin del
  calendario: por fecha o por número de ejecuciones totales.
- **`byweekday`** (opcional, 0-6) — para `freq` `weekly` o `monthly`,
  fija el día de la semana (0=lunes, 6=domingo).
- **`bymonthday`** (opcional) — para `freq` `monthly` o `yearly`, día
  del mes (1-31 o `-1` para el último día del mes). Para `freq: monthly`
  el máximo aceptado es 28 (para evitar meses sin ese día).
- **`bysetpos`** (opcional) — para `freq: monthly`, posición ordinal del
  día de la semana en el mes: 1-4 para "el primer/segundo/.../cuarto
  `byweekday`", `-1` para "el último `byweekday`". Requiere `byweekday`.
  Ejemplo: `byweekday: 2, bysetpos: -1` = "el último miércoles del mes".
- **`bymonth`** (opcional, 1-12) — para `freq: yearly`, mes del año.

Otros campos del trigger:

- **`daysAdvance`** — reservado para uso futuro. Indicar siempre `0`.

### Action: plantilla de factura

`content.main.action` describe qué documento se genera. Forma:

```json
"action": {
  "type": "createDocument",
  "template": {
    "type": "invoice",
    "main": {
      "docNumber": { "series": "FP" },
      "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3",
      "currency": "EUR",
      "lines": [
        {
          "quantity": 1,
          "unitPrice": 100,
          "tax": ["S_IVA_21"],
          "text": "Mantenimiento mensual"
        }
      ]
    }
  }
}
```

`template.main` es una estructura equivalente a la de una factura de
venta normal (ver [Facturas de venta](https://www.facturadirecta.com/desarrolladores/api/referencia/invoices/)). Puede incluir
`customFields`, un mapa de valores indexado por el ID estable `cfi_<uuid v4>`
de cada [campo personalizado](https://www.facturadirecta.com/desarrolladores/api/referencia/custom-fields/). Las definiciones borradas
siguen siendo válidas; un ID desconocido o con formato incorrecto produce
`400 Bad Request`.

Cuando llega el momento programado, FacturaDirecta crea una factura con los
datos de esta plantilla, asignando un nuevo número de documento y la fecha
actual.

## Enlace a la aplicación

Las respuestas que devuelven una factura recurrente incluyen `webUrl` junto al resto de
los datos. En los listados, cada elemento de `items` lleva su propio enlace.

`webUrl` abre la ficha del elemento en la aplicación web de FacturaDirecta.
Puedes mostrarlo como enlace en tu integración sin construir rutas internas.
El navegador pedirá iniciar sesión con un usuario que tenga acceso a la
empresa. Es un campo opcional y no es un endpoint de la API: trátalo como una
URL para el usuario, no como una URL a la que enviar `$ACCESS_TOKEN`.

## Operaciones

- [Lista de facturas recurrentes](#lista-de-facturas-recurrentes)
- [Crear factura recurrente](#crear-factura-recurrente)
- [Obtener una factura recurrente](#obtener-una-factura-recurrente)
- [Actualizar factura recurrente](#actualizar-factura-recurrente)
- [Borrar factura recurrente](#borrar-factura-recurrente)
- [Actualizar etiquetas](#actualizar-etiquetas)
- [Generar muestra en PDF](#generar-muestra-en-pdf)
- [Adjuntos](#adjuntos)

## Lista de facturas recurrentes

`GET /{companyId}/recurring/invoices` devuelve las facturas recurrentes
de la empresa, paginadas.

**Parámetros de consulta específicos:**

- **`minNextScheduledDate`**, **`maxNextScheduledDate`** — rango por la
  próxima ejecución programada (el `nextScheduledTime` de `related`), con
  los dos extremos incluidos. Útil para identificar qué recurrentes están
  a punto de ejecutarse o cuáles ya vencieron. Cada uno admite:
  - Una fecha y hora ISO 8601 (`YYYY-MM-DDTHH:mm:ss.sssZ`). Se compara el
    instante exacto, con su desfase horario si lo lleva (`+02:00`). Los
    `:` pueden ir sin codificar; el `+` se codifica como `%2B`, o llega
    como un espacio.
  - Solo la fecha (`YYYY-MM-DD`). Cuenta el día entero en la zona horaria
    de la empresa (por defecto, `Europe/Madrid`).
- **`search`** — búsqueda aproximada en el texto de la factura recurrente (título, líneas, notas) y en el nombre de su cliente: todas las palabras, sin distinguir acentos ni mayúsculas (ver [Búsqueda](https://www.facturadirecta.com/desarrolladores/api/referencia/search/)).
- **`series`** — serie de numeración de la factura plantilla.
- **`enabled`** — `true`/`false` para filtrar activas/inactivas.
- **`freq`** — frecuencia (`daily`/`weekly`/`monthly`/`yearly`).
- **`interval`** — intervalo entero.
- **`contact`** — ID de contacto de la factura plantilla.
- **`hasContact`** — `true`/`false`.
- **`minTotal`**, **`maxTotal`** — rango de importe de la plantilla.
- **`currency`** — código ISO 4217.
- **`country`** — código país ISO 3166-1 Alpha-2.
- **`allTheseTags`**, **`anyOfTheseTags`** — filtros por etiquetas concretas.
- **`hasTags`** — `true` devuelve solo facturas recurrentes con al menos una etiqueta y `false`, solo facturas recurrentes sin etiquetas.
- **`sortBy`** — campo de orden.
- **`related`** — valores admitidos:
  - `nextScheduledTime` — devuelve la próxima fecha-hora prevista.
  - `upcomingSceheduledTimes` — devuelve hasta las próximas 5 fechas
    programadas. (Nombre con typo histórico en el openapi; se mantiene
    por compatibilidad.)
  - `taxIds` — devuelve en `related.objects` el detalle de los impuestos
    aplicados en la plantilla.

**Parámetros globales aceptados:**

Acepta además los parámetros estándar `offset`, `limit`,
`minCreationDate`, `maxCreationDate`, `minModificationDate`,
`maxModificationDate` y el header `accept-version`. Ver
[Paginación](https://www.facturadirecta.com/desarrolladores/api/guias/pagination/) y
[Autenticación](https://www.facturadirecta.com/desarrolladores/api/guias/authentication/).

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices?enabled=true&freq=monthly&related=nextScheduledTime"
```

## Crear factura recurrente

`POST /{companyId}/recurring/invoices` crea una factura recurrente.

**Parámetros del body:**

- `content.type` — siempre `"automation"`.
- `content.main.title` (obligatorio) — nombre descriptivo.
- `content.main.enabled` (opcional, por defecto `true`).
- `content.main.trigger` (obligatorio) — ver [Trigger y rrule](#trigger-y-rrule).
- `content.main.action` (obligatorio) — ver [Action: plantilla de factura](#action-plantilla-de-factura).
- `tags` (opcional).

**Notas:**

- La primera ejecución no recupera fechas anteriores al día de creación. Si
  `dtstart` está en el pasado, el calendario conserva esa referencia, pero el
  servidor espera a la primera ocurrencia del mismo día o posterior. Así evita
  generar una factura histórica que no solicitaste.
- Si la primera ocurrencia vence por un error técnico, sigue pendiente durante
  el resto de su periodo. Una recurrente mensual, por ejemplo, puede recuperar
  esa primera factura hasta que llegue la siguiente ocurrencia mensual.
- Si `enabled: false`, la recurrente se guarda pero las ejecuciones
  programadas se omiten hasta que se reactive.
- Los planes 2026 limitan el número de recurrentes **activas a la vez**
  (Gratis 2, Bronce 20, Plata 200, Oro y Diamante sin límite; cuentan
  todas las automatizaciones activas, no solo las facturas). Con el cupo lleno, crear
  una recurrente con `enabled: true` devuelve `403` con el código
  `plan_limit_exceeded` (ver [Errores](https://www.facturadirecta.com/desarrolladores/api/guias/errors/)); crearla con
  `enabled: false` siempre es posible. Desactivar o borrar una recurrente
  libera hueco.
- La respuesta es la recurrente creada completa, en el mismo formato que
  [Obtener una factura recurrente](#obtener-una-factura-recurrente).

**Parámetros globales aceptados:** `accept-version`.

###### Ejemplo de request JSON

Recurrente mensual el día 1 de cada mes, desde junio de 2026 durante
12 meses:

```json
{
  "content": {
    "type": "automation",
    "main": {
      "title": "Mantenimiento mensual - Acme",
      "enabled": true,
      "trigger": {
        "type": "recurring",
        "rrule": {
          "freq": "monthly",
          "interval": 1,
          "dtstart": "2026-06-01T06:00:00.000Z",
          "count": 12,
          "bymonthday": 1
        }
      },
      "action": {
        "type": "createDocument",
        "template": {
          "type": "invoice",
          "main": {
            "docNumber": { "series": "FP" },
            "contact": "con_2c4f8e21-7b3a-4d9c-9e1f-a8b7c6d5e4f3",
            "currency": "EUR",
            "lines": [
              {
                "quantity": 1,
                "unitPrice": 100,
                "tax": ["S_IVA_21"],
                "text": "Mantenimiento mensual"
              }
            ]
          }
        }
      }
    }
  }
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '@recurringInvoice.json' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices"
```

## Obtener una factura recurrente

`GET /{companyId}/recurring/invoices/{id}` devuelve una factura
recurrente por ID.

**Parámetros específicos:**

- `related` (opcional) — mismos valores que en
  [Lista de facturas recurrentes](#lista-de-facturas-recurrentes)
  (`nextScheduledTime`, `upcomingSceheduledTimes`, `taxIds`).

**Parámetros globales aceptados:** `accept-version`.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f?related=nextScheduledTime&related=upcomingSceheduledTimes"
```

## Actualizar factura recurrente

`PUT /{companyId}/recurring/invoices/{id}` sustituye el contenido de la
recurrente. Dentro de `action.template` se aplica la misma regla que al
actualizar una factura: las líneas se reemplazan por las que envíes, los campos
opcionales que no envíes se eliminan y los obligatorios que no envíes conservan
su valor, igual que la serie (`docNumber`) y `meta`.

Casos típicos:

- Cambiar la plantilla (`action.template`): por ejemplo, subir el
  importe de la suscripción mensual. Los documentos ya emitidos no se
  ven afectados; solo las próximas ejecuciones generan facturas con el
  nuevo importe.
- Cambiar la frecuencia (`trigger.rrule`): el calendario conserva `dtstart`
  como referencia y descarta las ocurrencias anteriores al día de la
  actualización si todavía no hubo una ejecución correcta.
- Activar o desactivar (`enabled`): conserva la configuración. Al reactivar una
  recurrente sin ejecuciones correctas, no se recuperan ocurrencias anteriores
  al día de la actualización. Reactivar con el cupo de recurrentes activas del
  plan lleno devuelve `403` `plan_limit_exceeded`; editar una recurrente que ya
  está activa no consume hueco.

**Parámetros globales aceptados:** `accept-version`.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \
  -d '@recurringInvoice.json' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f"
```

## Borrar factura recurrente

`DELETE /{companyId}/recurring/invoices/{id}` elimina la recurrente. Las
facturas ya emitidas por la recurrente **se conservan**. Solo se cancela
el calendario de ejecuciones futuras.

Si quieres pausar temporalmente sin perder configuración, usa
`enabled: false` en lugar de borrar.

**Parámetros globales aceptados:** `accept-version`.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f"
```

## Actualizar etiquetas

`PUT /{companyId}/recurring/invoices/{id}/tags` reemplaza el conjunto
de etiquetas sin tocar el resto del contenido.

###### Ejemplo de request JSON

```json
{
  "tags": ["suscripcion", "premium"]
}
```

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \
  -d '{"tags":["suscripcion","premium"]}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/tags"
```

## Generar muestra en PDF

`PUT /{companyId}/recurring/invoices/{id}/pdf` genera una URL temporal
con una **muestra** de la factura que se emitiría, en PDF. Es solo una
previsualización: no crea ninguna factura real ni avanza el calendario.

**Parámetros del body:**

- `mode` (opcional) — `"attachment"` (URL para descargar como adjunto,
  por defecto), `"inline"` (visualización en línea) o `"print"` (URL
  HTML que carga el PDF y lo envía a imprimir).

**Parámetros globales aceptados:** `accept-version`.

###### Copy as cURL

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" -X PUT \
  -d '{"mode":"inline"}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/pdf"
```

## Adjuntos

Una factura recurrente puede tener adjuntos vinculados (p. ej. un
contrato base). Estos adjuntos viajan en cada factura generada por la
recurrente.

### Listar adjuntos

`GET /{companyId}/recurring/invoices/{id}/attachments` devuelve los
adjuntos vinculados.

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments"
```

### Vincular adjuntos

`POST /{companyId}/recurring/invoices/{id}/attachments` añade adjuntos
a partir de uploads previos.

**Parámetros del body:**

- `uploadIds` — array de identificadores `upl_<uuid>`. Mínimo uno.

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"uploadIds":["upl_6f8e9b42-1d3c-4a7f-b2e4-9c1d5e7f3a6b"]}' \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments"
```

### Eliminar un adjunto

`DELETE /{companyId}/recurring/invoices/{id}/attachments/{attachmentIndex}`
elimina un adjunto. `attachmentIndex` es la posición en el array
(basada en cero).

```shell
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" -X DELETE \
  "https://app.facturadirecta.com/api/$COMPANY_ID/recurring/invoices/aut_8a2c4e91-3b5d-4f7c-9e1a-2d4b6c8e0a1f/attachments/0"
```

## Recomendaciones

- **Empieza con `enabled: false`** mientras pruebas: la recurrente
  queda configurada sin generar facturas.
- **Usa `related=nextScheduledTime`** al consultar para saber cuándo
  se ejecutará por próxima vez. Y `upcomingSceheduledTimes` para ver
  las siguientes 5 fechas (útil para validar que el `rrule` resuelve
  al calendario que esperas).
- **Para suscripciones que cambian de precio**, actualiza la plantilla
  (`action.template.main.lines[].unitPrice`) en cualquier momento. Las
  ejecuciones futuras usan el nuevo precio sin tocar las pasadas.
- **`dtstart` en el futuro** es válido y útil para programar
  recurrentes que arranquen más adelante.
- **Para pausar temporalmente**, prefiere `enabled: false` sobre
  borrado. Conserva todo el calendario y la configuración.

## Errores comunes

- `400` — `minNextScheduledDate` o `maxNextScheduledDate` es una fecha o
  una fecha y hora que no existe, como `2026-02-30`. Ver [Fechas que no existen](https://www.facturadirecta.com/desarrolladores/api/guias/errors/#fechas-que-no-existen-400).
- `400 ValidationError` — `rrule` con combinación inválida (`until` y
  `count` a la vez, `bymonthday > 28` con `freq: monthly`, `bysetpos`
  sin `byweekday`...).
- `400 ValidationError` — `daysAdvance != 0` (reservado, debe quedar
  en cero).
- `400 ValidationError` — `action.template.type` distinto de `invoice`.
  Hoy es el único valor soportado.
- `409 Conflict` — `series` o `docNumber` de la plantilla en conflicto
  con otra factura emitida.

Ver [Errores y validaciones](https://www.facturadirecta.com/desarrolladores/api/guias/errors/) para el formato
general.

## Referencia exhaustiva

Esta página cubre los matices funcionales y los casos típicos. Para la
referencia exhaustiva de todos los campos del body y la respuesta,
consulta el [Swagger UI](https://www.facturadirecta.com/desarrolladores/api/explorador/) o el
[openapi crudo](https://app.facturadirecta.com/openapi.json).

## Endpoints

| Método | Path | operationId | Scopes | Descripción |
|---|---|---|---|---|
| GET | `/{companyId}/recurring/invoices` | `getRecurringInvoices` | `invoices:read`, `recurring:read` | Lista de facturas de venta recurrentes |
| GET | `/{companyId}/recurring/invoices/{id}` | `getRecurringInvoice` | `invoices:read`, `recurring:read` | Obtener una factura de venta recurrente |
| GET | `/{companyId}/recurring/invoices/{id}/attachments` | `getRecurringInvoiceAttachments` | `invoices:read`, `recurring:read` | Listar adjuntos de una factura recurrente |
| POST | `/{companyId}/recurring/invoices` | `createRecurringInvoice` | `invoices:write`, `recurring:write` | Crea factura de venta recurrente |
| POST | `/{companyId}/recurring/invoices/{id}/attachments` | `addRecurringInvoiceAttachments` | `invoices:write`, `recurring:write` | Vincular adjuntos a una factura recurrente |
| PUT | `/{companyId}/recurring/invoices/{id}` | `updateRecurringInvoice` | `invoices:write`, `recurring:write` | Actualizar factura recurrente |
| PUT | `/{companyId}/recurring/invoices/{id}/pdf` | `createRecurringInvoicePDF` | `invoices:read`, `recurring:read` | Generar muestra de la factura de venta recurrente en formato PDF |
| PUT | `/{companyId}/recurring/invoices/{id}/tags` | `updateRecurringInvoiceTags` | `invoices:write`, `recurring:write` | Actualizar etiquetas de factura de venta recurrente |
| DELETE | `/{companyId}/recurring/invoices/{id}` | `deleteRecurringInvoice` | `invoices:write`, `recurring:write` | Borrar factura de venta recurrente |
| DELETE | `/{companyId}/recurring/invoices/{id}/attachments/{attachmentIndex}` | `deleteRecurringInvoiceAttachment` | `invoices:write`, `recurring:write` | Eliminar adjunto de una factura recurrente |

## Scopes

- **`invoices:read`** — Lectura de facturas.
- **`invoices:write`** — Modificación de facturas.
- **`recurring:read`** — Lectura de documentos recurrentes.
- **`recurring:write`** — Modificación de documentos recurrentes.


Fuente: https://github.com/facturadirecta/api-docs/blob/a9c89bac0578614ce4cdbb89e099e60b7e71f80e/sections/recurring.md
