Facturas
API externa /external/v1/invoices: crear, emitir y enviar, listar, detalle, PDF, anular y estadísticas, con el plan y los permisos que exige cada endpoint.
Actualizado el . 9 min de lectura
Incluido en todos los planes. Ver precios
Con /external/v1/invoices tu aplicación emite facturas en nombre de
sus usuarios, con la numeración y la firma de tu organización, y las
consulta, descarga o anula. Todas las peticiones llevan la cabecera
X-Api-Key con una clave activa, exigen que la facturación de la
aplicación esté encendida y tienen un límite de 60 peticiones por
minuto.
Cada clave solo opera sobre las facturas de su propia aplicación, y el
userId filtra además por el usuario indicado.
Plan y permisos
Antes que el permiso de la aplicación se comprueba el plan de tu
organización. Sin el aplicativo Finanzas contratado, todos estos
endpoints responden 403 con errorCode: 'MODULE_NOT_ENABLED' y la
clave del aplicativo que falta.
| Endpoint | Aplicativo del plan | Permiso de la API (scope) |
|---|---|---|
POST /external/v1/invoices | Finanzas | crm:invoices:write y crm:invoices:issue |
GET /external/v1/invoices | Finanzas | crm:invoices:read |
GET /external/v1/invoices/:id | Finanzas | crm:invoices:read |
GET /external/v1/invoices/:id/pdf | Finanzas | crm:invoices:read |
GET /external/v1/invoices/stats | Finanzas | crm:invoices:read |
POST /external/v1/invoices/:id/cancel | Finanzas | crm:invoices:cancel |
Sin el permiso adecuado, los endpoints responden 403 INSUFFICIENT_SCOPE
con un mensaje genérico que no dice cuál falta. No hay periodo de
gracia: el permiso se exige desde la primera llamada, también en
aplicaciones creadas hace tiempo, así que concédelos antes de integrar.
crm:invoices:cancel es crítico financiero: solo lo concede un Super
Administrador, con un motivo. El catálogo completo y las reglas de
concesión están en Permisos de la API.
Las acciones sobre una factura ya emitida (enviarla por correo, cobrarla, marcarla pagada, devolver el dinero o pedir su enlace público) están en Acciones sobre una factura emitida.
Crear una factura
/api/external/v1/invoicesEmite una factura en nombre de la aplicación con la numeración de tu organización y firma VeriFactu si está activada. Como crea y emite en el mismo paso, exige a la vez crm:invoices:write y crm:invoices:issue.
Cuerpo de la petición
- userobjetoobligatorio
- Datos del usuario externo propietario de la factura.
- user.externalIdstringobligatorio
- Identificador estable del usuario en tu sistema (máximo 255 caracteres). Con él se crea o se actualiza el usuario.
- user.emailstringobligatorio
- Correo del usuario. Se valida su formato.
- user.namestring
- Nombre o razón social.
- user.taxIdstring
- NIF o CIF del usuario. Imprescindible si el receptor es el propio usuario y la factura es entre empresas. También puedes mandarlo antes con POST /external/v1/users/identify.
- user.billingAddressobjeto
- Dirección de facturación: street, city, postalCode y country obligatorios, province opcional. Si la pasas, sustituye a la guardada. También puedes mandarla antes con identify.
- user.metadataobjeto
- Datos libres asociados al usuario.
- recipientobjetoobligatorio
- Datos fiscales del receptor que se imprimirán en la factura.
- recipient.namestringobligatorio
- Nombre o razón social del receptor.
- recipient.taxIdstring
- NIF o CIF. Obligatorio en facturas entre empresas.
- recipient.addressstringobligatorio
- Dirección (calle y número).
- recipient.citystringobligatorio
- Ciudad.
- recipient.postalCodestringobligatorio
- Código postal.
- recipient.countrystringobligatorio
- País (mejor en castellano, por ejemplo «España»).
- recipient.emailstring
- Correo del receptor.
- recipient.phonestring
- Teléfono del receptor.
- issueDatestring
- Fecha de emisión (AAAA-MM-DD). Si la omites, se usa hoy.
- dueDatestring
- Fecha de vencimiento (AAAA-MM-DD).
- taxSystemenum
- IVA, IGIC, IPSI, EXEMPT o INTERNATIONAL. Si lo omites, se toma del régimen fiscal de tu organización.
- notesstring
- Notas visibles al cliente (se imprimen en el PDF).
- discountPercentnumber
- Descuento global (de 0 a 100).
- metadataobjeto
- Datos libres asociados a la factura.
- linesarrayobligatorio
- Líneas de la factura. Mínimo una.
- lines[].namestringobligatorio
- Concepto breve.
- lines[].descriptionstring
- Descripción extendida.
- lines[].quantitynumberobligatorio
- Cantidad (hasta 4 decimales).
- lines[].unitPricenumberobligatorio
- Precio unitario sin impuestos.
- lines[].taxTypeenumobligatorio
- Tipo de impuesto (por ejemplo, IVA_21 o IGIC_7).
- lines[].discountnumber
- Descuento en importe por línea.
- lines[].igicOperationTypeenum
- Solo para el régimen especial del comerciante minorista del IGIC: IGIC_RETAIL (venta de la actividad minorista, exenta; exige taxType EXEMPT) o IGIC_GENERAL (resto). Exige que tu organización tenga ese régimen declarado en su configuración fiscal.
- lines[].exemptionReasonenum
- Causa de la exención: EXEMPT_IGIC_RETAILER, EXEMPT_IGIC_INTERNAL, EXEMPT_EXPORT o EXEMPT_OTHER. Obligatoria cuando taxType es EXEMPT con IGIC. Decide la mención legal que se imprime.
- lines[].exemptionNotestring
- Explicación de la exención (máximo 300 caracteres). Obligatoria con EXEMPT_OTHER; se imprime en la mención legal de la factura.
- sendEmailboolean
- Envía la factura por correo al emitirla. Si lo omites, manda la preferencia de la aplicación (interruptor «Enviar las facturas por correo al emitirlas»). El envío ocurre después del cierre fiscal: un fallo de correo nunca tumba una factura ya numerada.
- idempotencyKeystring
- Clave de idempotencia. Si llega la misma en 24 horas, se devuelve la factura original; si llega con otro contenido, 409 BILLING_IDEMPOTENCY_CONFLICT.
Si el bloque user no trae taxId o billingAddress y el usuario
tampoco los tiene guardados, se toman del recipient que se imprime en
el documento y se escriben en su ficha. Lo que declare user manda
siempre, y un dato ya guardado nunca se pisa con el del receptor. Es
una red, no la vía principal: manda los datos fiscales con
identify en cuanto los tengas,
porque una factura emitida a un cliente sin identificador fiscal obliga
a rectificarla.
Ejemplo de petición
curl -X POST https://os.utilia.ai/api/external/v1/invoices \
-H "X-Api-Key: utl_live_..." \
-H "Content-Type: application/json" \
-d '{
"user": {
"externalId": "user_01HXYZ",
"email": "cliente@empresa.com",
"name": "Empresa de Canarias SL",
"taxId": "B76543210"
},
"recipient": {
"name": "Empresa de Canarias SL",
"taxId": "B76543210",
"address": "Avenida de Canarias 12",
"city": "Las Palmas",
"postalCode": "35001",
"country": "España"
},
"issueDate": "2026-04-19",
"dueDate": "2026-05-19",
"taxSystem": "IGIC",
"lines": [
{
"name": "Plan Premium mensual",
"quantity": 1,
"unitPrice": 49.95,
"taxType": "IGIC_7"
}
],
"sendEmail": true,
"idempotencyKey": "ord_01HXYZ-invoice"
}'Respuesta correcta (201)
{
"id": "ckinv1234567890",
"invoiceNumber": "202600042",
"status": "SENT",
"issuancePending": false,
"cancellationPending": false,
"totalAmount": 53.45,
"paidAmount": 0,
"currency": "eur",
"issueDate": "2026-10-01T00:00:00.000Z",
"dueDate": "2026-10-08T00:00:00.000Z",
"pdfUrl": "/external/v1/invoices/ckinv1234567890/pdf",
"externalUserId": "user_01HXYZ",
"createdAt": "2026-10-01T09:12:44.000Z",
"idempotent": false,
"emailSent": true
}- emailSentboolean
- Resultado del envío por correo al emitir. Solo viaja cuando se ha intentado. En falso, la factura SÍ está emitida y numerada y solo ha fallado el correo: reenvíala con POST /external/v1/invoices/:id/send. No la vuelvas a crear nunca.
- pdfUrlstringobligatorio
- Ruta relativa para descargar el PDF desde tu servidor con la misma clave de API. No es un enlace para tu usuario final: para eso está el enlace público.
- idempotentbooleanobligatorio
- Indica si la respuesta es una factura ya existente, reutilizada por la clave de idempotencia.
El PDF de la API externa no es un enlace público
pdfUrl exige la cabecera X-Api-Key y no debe salir de tu servidor.
Para entregar la factura a tu usuario final, emite el enlace público con
POST /external/v1/invoices/:id/public-link.
Reintentar sin duplicar la factura
Si se corta la conexión, no sabes si la factura llegó a emitirse.
Repite la petición con la misma idempotencyKey: durante 24 horas,
UTILIA OS reconoce la clave y no emite otra.
Reintentar con la misma clave de idempotencia
Participan: Tu servidor, UTILIA OS.
De Tu servidor a UTILIA OS:
POST /api/external/v1/invoices
Con idempotencyKey: ord_01HXYZ-invoice.Nota en UTILIA OS:
Numera la factura, la emite y responde, pero la respuesta se pierde por el camino.De Tu servidor a UTILIA OS:
POST /api/external/v1/invoices
La misma clave y el mismo cuerpo.De UTILIA OS a Tu servidor:
La misma factura, con idempotent: true (respuesta)
No se emite ninguna factura nueva.
¿Qué pasa si repites una clave de idempotencia?
¿Usaste esa idempotencyKey en las últimas 24 horas?
No:
Factura nueva
idempotent: false
Se numera y se emite otra factura.Sí:
¿Envías el mismo cuerpo?
Sí:
La misma factura
idempotent: true
No se emite ninguna factura nueva.No:
Conflicto
409 BILLING_IDEMPOTENCY_CONFLICT
Una clave sirve para una sola factura. Usa una nueva para otra factura.
Errores al crear
403 MODULE_NOT_ENABLED: el plan de tu organización no incluye el aplicativo Finanzas. Es una decisión comercial, no un fallo técnico: no reintentes.403 INSUFFICIENT_SCOPE: la aplicación no tiene concedidocrm:invoices:writeocrm:invoices:issue. La creación exige los dos, porque crea y emite en el mismo paso.403 BILLING_NOT_ENABLED: la facturación de la aplicación está apagada. Se enciende en la pantalla Facturación de la aplicación.409 BILLING_IDEMPOTENCY_CONFLICT: la clave de idempotencia ya se usó con otro contenido.422 BILLING_AMOUNT_EXCEEDS_LIMIT: el total supera el tope por factura configurado para la aplicación.400 VALIDATION_ERROR: uno o más campos no cumplen las restricciones.
Listar facturas
/api/external/v1/invoicesLista paginada de facturas de la aplicación, con filtros opcionales por usuario y estado. Permiso: crm:invoices:read.
Parámetros de consulta
- userIdstring
- Filtra por externalId del usuario.
- statusenum
- DRAFT, ISSUED, SENT, PAID, PARTIALLY_PAID, OVERDUE, CANCELLED, DISPUTED, OVERDUE_UNCOLLECTABLE, REFUNDED, PARTIALLY_REFUNDED o RECTIFIED.
- fromDatedate
- Fecha de emisión mínima (AAAA-MM-DD).
- toDatedate
- Fecha de emisión máxima (AAAA-MM-DD).
- pageinteger
- Página (por defecto, 1).
- limitinteger
- Elementos por página (por defecto 20, máximo 100).
Respuesta (200)
{
"invoices": [
{
"id": "inv-uuid",
"invoiceNumber": "202600001",
"status": "PAID",
"issueDate": "2026-04-19",
"dueDate": "2026-05-19",
"paidAt": "2026-04-20T11:22:33.000Z",
"totalAmount": 53.45,
"paidAmount": 53.45,
"currency": "EUR",
"externalUserId": "user_01HXYZ"
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
}
}Detalle de factura
/api/external/v1/invoices/:idDatos completos de una factura: líneas, desglose de impuestos y receptor. Permiso: crm:invoices:read.
Parámetros
- idpathobligatorio
- Identificador de la factura en UTILIA OS.
- userIdqueryobligatorio
- externalId del usuario propietario.
Errores del detalle
403 INSUFFICIENT_SCOPE: la aplicación no tiene concedidocrm:invoices:read.404 EXTERNAL_USER_NOT_FOUND: no hay ningún usuario de la aplicación con eseuserId.404 INVOICE_NOT_ACCESSIBLE: la factura no existe, es de otra aplicación o no es de ese usuario. Es siempre la misma respuesta, para no revelar si la factura existe.
Descargar PDF
/api/external/v1/invoices/:id/pdfPDF fiscal de la factura (application/pdf). Permiso: crm:invoices:read.
Parámetros
- idpathobligatorio
- Identificador de la factura.
- userIdqueryobligatorio
- externalId del usuario propietario.
Devuelve el binario con Content-Type: application/pdf y Content-Disposition: attachment; filename="factura-...pdf".
Anular factura
/api/external/v1/invoices/:id/cancelAnula una factura emitida y genera el registro VeriFactu de anulación si corresponde. Permiso: crm:invoices:cancel, que solo concede un Super Administrador.
Cuerpo
- reasonstring
- Motivo de la anulación. Se guarda en las notas internas de la factura.
userId (el externalId del propietario) va como parámetro de consulta, igual que en el detalle.
Errores de la anulación
403 INSUFFICIENT_SCOPE: la aplicación no tiene concedidocrm:invoices:cancel.409 INVOICE_STATE_INVALID: no se pueden anular borradores.409 INVOICE_ALREADY_CANCELLED: ya estaba anulada.409 BILLING_IDEMPOTENCY_CONFLICT: la emisión o una anulación anterior aún no ha terminado.
Estadísticas del usuario
/api/external/v1/invoices/statsTotales de facturación de un usuario externo. Permiso: crm:invoices:read.
Parámetros de consulta
- userIdstringobligatorio
- externalId del usuario.
Respuesta (200)
{
"total": 12,
"paid": 8,
"pending": 3,
"overdue": 1,
"totalAmount": 641.40,
"totalPaid": 427.60,
"totalPending": 213.80,
"currency": "eur"
}- totalnumberobligatorio
- Total de facturas emitidas a ese usuario externo.
- paidnumberobligatorio
- Facturas en estado PAID.
- pendingnumberobligatorio
- Facturas en estado ISSUED, SENT o PARTIALLY_PAID.
- overduenumberobligatorio
- Facturas en estado OVERDUE.
- totalAmountnumberobligatorio
- Suma del importe total facturado.
- totalPaidnumberobligatorio
- Suma del importe ya cobrado.
- totalPendingnumberobligatorio
- Diferencia entre lo facturado y lo cobrado (nunca negativa).
- currencystringobligatorio
- Divisa de tu organización en minúsculas (por ejemplo,
eur).
Cabeceras comunes
- X-Api-Keyheaderobligatorio
- Clave de API de la aplicación externa.
- X-Request-Idheader
- Identificador para seguir la petición; UTILIA OS lo devuelve en la respuesta.
- Content-Typeheaderobligatorio
- application/json en las peticiones con cuerpo.
Límite de uso
Todos los endpoints de este grupo comparten un límite de 60 peticiones por minuto por clave, además del límite general de la clave (1.000 peticiones por hora). Al superarlo, la respuesta es 429 Too Many Requests con la cabecera Retry-After, que indica los segundos que hay que esperar.
Siguiente paso
- Acciones sobre una facturaEnviar, cobrar, marcar pagada, devolver y enlace público.
- SuscripcionesCuotas que se generan, envían y cobran solas cada periodo.
- Contenido de los avisosLos avisos de facturación, suscripciones y cobros.
- Guía de facturaciónEl recorrido completo, del alta del usuario al cobro recurrente.