Facturas recurrentes
Una plantilla recurrente genera facturas de forma periódica según un calendario. Desde la API puedes calcular el calendario sin guardar, crear y editar plantillas, gestionar cada fecha y actualizar precios en bloque.
Todas las llamadas usan la URL base https://app.faxtal.com/api/v1.1 y la cabecera X-API-Key (ver Autenticación).
| Operación | Scope de la API key |
|---|---|
| Consultar, vista previa de calendario y de precios | recurring:read |
| Crear, editar, pausar, reanudar, finalizar y gestionar fechas | recurring:write |
Plantilla que emite (issue o issue_and_send) | además, invoices:issue |
| Actualizar el catálogo desde una actualización de precios | además, parties:write |
| Propuesta desde una factura emitida | además, invoices:read |
Sin el scope necesario, la API responde 403 con el código scope_missing. Si el plan de la empresa no incluye la función, responde 402 con feature_outside_plan.
Plantillas
| Método y ruta | Scope | Qué hace |
|---|---|---|
POST /recurring-templates/preview | recurring:read | Calcula el calendario sin guardar nada |
GET /recurring-templates | recurring:read | Lista las plantillas. Filtro opcional status=active|paused|finished |
POST /recurring-templates | recurring:write | Crea una plantilla. Responde 201 |
GET /recurring-templates/{template_id} | recurring:read | Consulta una plantilla |
PUT /recurring-templates/{template_id} | recurring:write | Edita una plantilla |
POST /recurring-templates/{template_id}/pause | recurring:write | Pausa la plantilla |
POST /recurring-templates/{template_id}/resume | recurring:write | La reanuda |
POST /recurring-templates/{template_id}/finish | recurring:write | La finaliza. No se puede deshacer |
GET /recurring-templates/from-invoice/{invoice_id} | recurring:read e invoices:read | Propuesta de plantilla desde una factura emitida. No guarda |
Cuerpo de una plantilla
| Campo | Descripción |
|---|---|
name | Nombre de la plantilla. |
canonical_template | Canónico v1.1 parcial: {"invoice": {"customer", "header", "issue_data", "lines", "payment_details"}}. |
schedule | Calendario (ver la tabla siguiente). |
issue_mode | draft, issue o issue_and_send. |
payment_method | transfer, sepa_debit u other. |
sepa_mandate_id | public_id de un mandato activo del cliente. Obligatorio con payment_method: "sepa_debit", que además exige third_party_id y la funcionalidad de tesorería SEPA del plan. La salida de la plantilla lo devuelve. |
series_code | Serie de emisión. |
third_party_id | Cliente de la plantilla. |
email_template_code | Plantilla de email para issue_and_send. |
Calendario (schedule):
| Campo | Descripción |
|---|---|
frequency | Frecuencia: semanal, mensual, bimestral, trimestral, semestral o anual. |
interval | Cada cuántos periodos. |
anchor_day | Día del mes. |
start_date | Fecha de inicio. |
end_mode | Cómo termina: sin fin, en una fecha o tras un número de facturas. |
end_date | Fecha de fin, si end_mode es en una fecha. |
max_occurrences | Número de facturas, si end_mode es por número. |
period_mode | Periodo facturado: el actual o el vencido. |
due_days | Días hasta el vencimiento. |
En los textos del canónico puedes usar las variables {mes}, {mes+1}, {mes-1}, {trimestre}, {año}, {inicio_periodo}, {fin_periodo} y {n}, que se resuelven con la fecha de cada factura.
Si al generar una factura el mandato ya no está activo, esa ocurrencia falla con sepa_mandate_inactive y la plantilla se pausa con paused_reason igual a mandate_inactive. Para reanudarla, edítala con otro sepa_mandate_id.
Calendario sin guardar
POST /api/v1.1/recurring-templates/preview recibe {canonical_template, schedule} y devuelve las fechas, los periodos y los importes. No crea nada.
curl -X POST "https://app.faxtal.com/api/v1.1/recurring-templates/preview" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-H "Content-Type: application/json" \
-d '{
"canonical_template": { "invoice": { "lines": [] } },
"schedule": {
"frequency": "monthly",
"interval": 1,
"anchor_day": 1,
"start_date": "2026-11-01",
"end_mode": "never",
"period_mode": "current",
"due_days": 30
}
}'
Bloqueo optimista
PUT y las acciones pause, resume y finish llevan el campo version de la plantilla, tal como la devolvió la última lectura. Si otra persona la cambió entre medias, la API responde 409 con recurring_template_version_conflict: vuelve a leerla y repite.
curl -X POST "https://app.faxtal.com/api/v1.1/recurring-templates/42/pause" \
-H "X-API-Key: fctXXXXXXXX_YYYYYYYYYYYYYYYY" \
-H "Content-Type: application/json" \
-d '{"version": 3}'
Ocurrencias
Cada fecha del calendario es una ocurrencia, identificada por su seq.
| Método y ruta | Scope | Qué hace |
|---|---|---|
GET /recurring-templates/{template_id}/occurrences | recurring:read | Lista las ocurrencias con su estado |
POST /recurring-templates/{template_id}/occurrences/{seq}/generate | recurring:write | Genera la factura ahora. La plantilla tiene que estar activa |
POST /recurring-templates/{template_id}/occurrences/{seq}/skip | recurring:write | Salta la fecha |
POST /recurring-templates/{template_id}/occurrences/{seq}/restore | recurring:write | La recupera |
PATCH /recurring-templates/{template_id}/occurrences/{seq} | recurring:write | Cambia la fecha con {"planned_date": "2026-12-05"}. No puede ser pasada |
Idempotencia
Cada factura generada lleva meta.external_ref = rec:<plantilla>:<seq>. Si reintentas una generación, no se duplica la factura.
Actualizar precios
| Método y ruta | Scope | Qué hace |
|---|---|---|
POST /recurring-templates/price-updates/preview | recurring:read | Calcula el cambio sin aplicarlo |
POST /recurring-templates/price-updates | recurring:write | Aplica el cambio. Responde 201 |
GET /recurring-templates/price-updates | recurring:read | Lista las actualizaciones. Filtro opcional template_id |
GET /recurring-templates/price-updates/{id} | recurring:read | Consulta una actualización |
Cuerpo de la vista previa:
{
"scope": {
"template_ids": [],
"third_party_ids": [],
"article_codes": ["CONSULT-SENIOR"],
"series_codes": []
},
"criterion": "percent",
"value": 3.5,
"rounding_step": 0.05,
"update_products": false
}
| Campo | Descripción |
|---|---|
scope | Plantillas, clientes, productos y series a los que afecta. Sin filtros, todas las plantillas activas y pausadas. Con article_codes solo cambian esas líneas. |
criterion | percent, amount (importe por unidad, positivo o negativo) o new_price. |
value | El porcentaje, el importe o el precio nuevo. |
rounding_step | null (al céntimo), 0.05, 0.10, 0.50 o 1. |
update_products | Si es true, aplica el mismo cambio al producto del catálogo con ese código, si su precio va en el mismo modo (con o sin IVA). Exige parties:write. |
La vista previa devuelve el precio antes y después por línea, el importe de la próxima factura y del calendario, y la version de cada plantilla en templates[]. Para aplicar, envía el mismo cuerpo más template_versions: un objeto {template_id: version} con esas versiones. Si alguna plantilla cambió en medio, la API responde 409 con recurring_price_update_stale: repite la vista previa.
Aplicar cambia las plantillas y sus fechas pendientes. Las facturas ya generadas no cambian, y la actualización no se puede deshacer: se corrige con otra.
Errores
Sin el plan o el scope necesario, la API responde 402 o 403 (ver arriba). El resto de errores traen el code de esta tabla. Los conflictos de versión (recurring_template_version_conflict y recurring_price_update_stale) responden 409.
code | Causa |
|---|---|
feature_outside_plan | El plan de la empresa no incluye las facturas recurrentes. |
scope_missing | Falta algún scope de los de la primera tabla. |
recurring_template_not_found | La plantilla no existe o es de otra empresa. |
recurring_template_version_conflict | La version enviada no es la actual. |
recurring_price_update_stale | Alguna plantilla cambió entre la vista previa y la aplicación. |
recurring_template_not_active | La operación exige una plantilla activa. |
recurring_template_finished | La plantilla está finalizada. |
recurring_unknown_variables | Los textos usan variables que no existen. |
recurring_send_requires_customer | issue_and_send necesita un cliente. |
recurring_occurrence_not_editable | Esa fecha ya no se puede cambiar. |
recurring_occurrence_date_in_past | La fecha nueva es anterior a hoy. |
recurring_price_negative | El cambio deja un precio en negativo. |
recurring_price_update_empty | El cambio no afecta a ninguna plantilla. |
products_feature_required | update_products exige el catálogo de productos en el plan. |
recurring_sepa_feature_required | sepa_debit exige la funcionalidad de tesorería SEPA del plan. |
recurring_sepa_requires_customer | sepa_debit necesita un cliente (third_party_id). |
recurring_sepa_mandate_required | Falta sepa_mandate_id. |
recurring_sepa_mandate_other_customer | El mandato es de otro cliente. |
sepa_mandate_inactive | El mandato no está activo. |
Servidor MCP
Cinco tools del Servidor MCP trabajan con las plantillas recurrentes.
Ver también
- Formato canónico: estructura del canónico de la plantilla.
- Terceros y productos.
- Errores: formato común de las respuestas de error.
- Facturas recurrentes en la aplicación.