API para desarrolladores
Emite facturas legales (VeriFactu) desde tu tienda online, tu web o tu propio programa. Envías el pedido y EmpresaFactura pone la numeración, el IVA, el registro VeriFactu, el QR y el PDF.
- 1. Resumen
- 2. Requisitos
- 3. Autenticación y claves de API
- 4. Inicio rápido
- 5. GET /whoami: comprobar la clave
- 6. POST /invoices: emitir una factura
- 6.1. Cobros: método de pago, nº de autorización y log JSON
- 6.2. Modo copia: tu programa ya emite la factura
- 7. GET /invoices/:id: consultar una factura
- 8. GET /invoices/:id/pdf: descargar el PDF
- 9. POST /received-invoices: registrar una factura recibida
- 10. El objeto factura
- 11. Reglas de facturación
- 12. Idempotencia y reintentos
- 13. Errores
- 14. Límites de uso
- 15. Ejemplos en PHP, Node.js y Python
- 16. Plugins: WooCommerce, PrestaShop y WHMCS
- 17. Qué no hace la API
- 18. Buenas prácticas y seguridad
1. Resumen
| URL base | https://api.empresafactura.com/api/v1 |
|---|---|
| Formato | JSON en UTF-8. Envía Content-Type: application/json en las peticiones con cuerpo. |
| Autenticación | Cabecera X-API-Key con una clave de tu empresa. |
| Uso | De servidor a servidor. La API no admite llamadas desde el navegador (CORS cerrado) para que tu clave no quede expuesta. |
| Versión | v1. Los cambios incompatibles irán en una versión nueva; en v1 solo se añaden campos. |
Todas las respuestas JSON tienen la misma forma:
// Correcto
{ "success": true, "data": { ... } }
// Error
{ "success": false, "message": "Texto del error" }
Los endpoints son cinco:
| Método y ruta | Para qué sirve |
|---|---|
GET/whoami | Comprueba la clave y devuelve la empresa a la que pertenece. |
POST/invoices | Emite una factura completa o simplificada a partir de un pedido. |
GET/invoices/:id | Devuelve la factura con su estado y su registro VeriFactu (QR). |
GET/invoices/:id/pdf | Descarga el PDF de la factura. |
POST/received-invoices | Registra una factura de compra (de un proveedor) con su PDF original adjunto. |
2. Requisitos
- Una cuenta de EmpresaFactura con el módulo API e integraciones activo. Si lo desactivas, todas las claves dejan de funcionar al momento (error
403). - Un usuario con permiso sobre API para crear y revocar claves.
- En Ajustes › Impuestos, los tipos de IVA que vayas a usar (21, 10, 4, 0…). La API no cambia un IVA que no exista: devuelve error.
- Los datos fiscales de la empresa completos, igual que para facturar desde la aplicación.
3. Autenticación y claves de API
Crear una clave
- Entra en app.empresafactura.com y abre el menú API e integraciones.
- Pulsa Añadir y ponle un nombre que diga dónde se usa (por ejemplo, «WooCommerce tienda.es»).
- Copia la clave. Solo se muestra una vez: se guarda cifrada y nadie puede volver a verla, tampoco nosotros. Si la pierdes, crea otra y revoca la anterior.
La clave tiene este formato y va ligada a una sola empresa:
efk_<empresa>_<40 caracteres hexadecimales>
efk_mitienda_3f9a1c0b7d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a
Si tienes varias empresas, crea una clave en cada una. La clave decide en qué empresa se emite la factura.
Enviar la clave
En cada petición, en la cabecera X-API-Key:
X-API-Key: efk_mitienda_3f9a1c0b7d2e4f6a8b1c3d5e7f9a0b2c4d6e8f0a
También se acepta Authorization: Bearer <clave>, por si tu herramienta solo permite esa cabecera.
Revocar y rotar
En API e integraciones verás cada clave con su prefijo, la fecha de creación y la del último uso. Revocar la anula al momento y no tiene vuelta atrás. Para rotar sin cortar el servicio: crea la nueva, cámbiala en tu integración y después revoca la antigua.
4. Inicio rápido
1. Comprueba la clave:
curl https://api.empresafactura.com/api/v1/whoami \
-H "X-API-Key: TU_CLAVE"
2. Emite una factura simplificada (ticket, sin NIF del comprador):
curl -X POST https://api.empresafactura.com/api/v1/invoices \
-H "X-API-Key: TU_CLAVE" \
-H "Content-Type: application/json" \
-d '{
"externalId": "web:pedido-1001",
"invoiceType": "simplified",
"customer": { "name": "Ana Pérez" },
"lines": [ { "description": "Camiseta", "quantity": 2, "vatRate": 21, "total": 30.00 } ]
}'
3. Descarga el PDF con el id que te devuelve:
curl https://api.empresafactura.com/api/v1/invoices/ID_DE_LA_FACTURA/pdf \
-H "X-API-Key: TU_CLAVE" -o factura.pdf
5. GET/whoami: comprobar la clave
Sirve para probar la conexión al configurar una integración. No crea nada.
{
"success": true,
"data": {
"slug": "mitienda",
"keyName": "WooCommerce tienda.es",
"company": "Mi Tienda SL",
"stats": { "clients": 152, "invoices": 1040, "products": 87 }
}
}
Con una clave inexistente, mal copiada o revocada devuelve 401.
6. POST/invoices: emitir una factura
Emite y registra en VeriFactu la factura de un pedido. Es idempotente: con el mismo externalId nunca se crean dos facturas (ver Idempotencia).
Cuerpo de la petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
externalId | texto (máx. 120) | Sí | Identificador único del pedido en tu sistema. Recomendado: origen:dominio:número, por ejemplo woo:mitienda.es:1234. |
source | texto (máx. 60) | No | Nombre del programa que factura: woocommerce, prestashop, whmcs, mi-erp… Se guarda en la factura junto al externalId. Si no lo envías, se guarda el nombre de la clave de API. |
invoiceType | "full" | "simplified" | No | full: factura completa (F1), exige NIF. simplified: factura simplificada (F2), sin datos del destinatario. Si no lo envías: full si llega customer.nif, si no simplified. |
customer | objeto | En full | Datos del comprador (tabla siguiente). |
lines | lista | Sí (mín. 1) | Líneas de la factura (ver más abajo). |
issueDate | fecha ISO 8601 | No | Fecha de expedición, por ejemplo "2026-10-06". Por defecto, el momento de la petición. |
notes | texto (máx. 1000) | No | Observaciones que aparecen en la factura, por ejemplo "Pedido web #1234". |
paid | booleano | No | Solo en full. Con true se registra el cobro en la fecha de la factura y queda PAID. Úsalo si la tienda ya cobró, para que no salga como vencida. Si envías payments, no hace falta. |
payments | lista | No | Cobros del pedido con su método, su nº de autorización y el log del pago (ver Cobros). En full y en simplified. |
Cobros (payments[])
Cada cobro queda registrado en la factura, con su forma de pago, y entra en la tesorería y en la conciliación bancaria de la empresa. Si la empresa no tiene esa forma de pago, se crea sola. Luego, en Ajustes, se le asigna su cuenta bancaria o su caja.
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
method | texto | Sí | card (tarjeta online: Redsys, Stripe…), card_terminal (datáfono), bizum, cash (efectivo), cash_machine (cajón automático: CashKeeper, Cashlogy…), transfer, direct_debit (domiciliación), paypal, voucher (vale o tarjeta regalo), check (cheque o pagaré) u other. |
authCode | texto (máx. 120) | No | Nº de autorización de la tarjeta o de Bizum, id de la transacción o referencia de la transferencia. |
data | objeto JSON (máx. 8 KB) | No | Log del pago tal cual lo da la pasarela o el terminal. Por ejemplo, los parámetros Ds_* de Redsys ya descodificados, o el ticket del datáfono. |
amount | número | Si hay varios | Importe cobrado. Con un solo cobro, por defecto, el total de la factura. Si hay varios cobros (pago mixto), es obligatorio en todos. |
date | fecha ISO 8601 | No | Momento del cobro. Por defecto, la fecha de la factura. |
El log se guarda saneado. Se quitan las firmas, las claves, el CVV y Ds_MerchantParameters. Un número de tarjeta completo queda como ******0004, y un teléfono (por ejemplo, el de Bizum), con solo sus 3 últimas cifras. Aun así, no envíes datos de tarjeta que no necesites.
"payments": [
{
"method": "bizum",
"authCode": "123456",
"data": { "Ds_Order": "000012345678", "Ds_AuthorisationCode": "123456", "Ds_Response": "0000" }
}
]
// Pago mixto en tienda física: parte en CashKeeper y parte con datáfono
"payments": [
{ "method": "cash_machine", "amount": 20.00 },
{ "method": "card_terminal", "amount": 25.60, "authCode": "A1B2C3", "data": { "terminal": "T-01", "brand": "VISA", "card": "******0004" } }
]
Si la suma de los cobros no llega al total, la factura queda PARTIALLY_PAID. Lo que pase del total se ignora. Al reenviar el mismo externalId no se duplican los cobros.
Objeto customer
| Campo | Descripción |
|---|---|
nif | NIF/CIF/NIE del comprador. Obligatorio en full. Se aceptan espacios, puntos y guiones ("b-12.345.678" → B12345678). |
name | Nombre de la persona. En las simplificadas es el único dato que se guarda (como referencia). |
company | Razón social. Si viene, es el nombre fiscal del cliente; si no, se usa name. |
email, phone | Contacto. |
address, postalCode, city, province | Dirección fiscal. |
country | País. Por defecto, "España". |
En las facturas completas el cliente se busca por NIF en tu empresa. Si ya existe, se usa tal como está en EmpresaFactura (sus datos no se sobrescriben). Si no existe, se crea con los datos de customer; en ese caso hace falta company o name.
Objeto de cada línea (lines[])
| Campo | Tipo | Descripción |
|---|---|---|
description | texto (máx. 500) | Concepto. Si va vacío: «Artículo». |
quantity | número > 0 | Unidades. Por defecto, 1. |
vatRate | número | Obligatorio. % de IVA: 21, 10, 4, 0… Tiene que existir en los IVA de la empresa. |
total | número | Importe total de la línea con IVA (lo que se cobró). Se respeta al céntimo. Es la opción recomendada. |
unitPrice | número | Alternativa a total: precio unitario sin IVA. Solo se usa si no envías total. |
Envía siempre total si tu tienda trabaja con precios con IVA. Así el total de la factura cuadra al céntimo con lo cobrado. Si hay descuentos o cupones, repártelos entre las líneas antes de enviarlas: no existe una línea de descuento aparte.
Ejemplo: factura completa ya cobrada
POST /api/v1/invoices
X-API-Key: TU_CLAVE
Content-Type: application/json
{
"externalId": "woo:mitienda.es:1234",
"source": "woocommerce",
"invoiceType": "full",
"customer": {
"name": "Ana Pérez",
"company": "Pérez Diseño SL",
"nif": "B12345678",
"email": "ana@perezdiseno.es",
"address": "Rúa do Progreso 12",
"postalCode": "27001",
"city": "Lugo",
"province": "Lugo",
"country": "España"
},
"lines": [
{ "description": "Camiseta talla M", "quantity": 2, "vatRate": 21, "total": 30.00 },
{ "description": "Libro", "quantity": 1, "vatRate": 4, "total": 15.60 }
],
"notes": "Pedido web #1234",
"payments": [
{ "method": "card", "authCode": "123456", "data": { "gateway": "redsys", "Ds_Order": "000012345678", "Ds_Card_Brand": "1" } }
]
}
Respuesta
201 Created si la factura es nueva; 200 OK con "replayed": true si ese externalId ya estaba facturado.
{
"success": true,
"data": {
"id": "0192f3a4-7c1e-7b2d-9a51-6c0e8d4f2a10",
"number": "F2026-000123",
"status": "PAID",
"issueDate": "2026-10-06T09:30:00.000Z",
"subtotal": 39.79,
"totalVat": 5.81,
"total": 45.60,
"type": "F1",
"externalId": "woo:mitienda.es:1234",
"source": "woocommerce",
"verifactu": {
"status": "GENERATED",
"qrUrl": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?…",
"hash": "3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60"
},
"replayed": false
}
}
Guarda el id: lo necesitas para consultar la factura y descargar el PDF.
Si tu programa ya emite la factura (modo copia)
Si tu tienda o tu programa ya emite la factura y la registra en VeriFactu (por ejemplo, WHMCS o PrestaShop con un módulo VeriFactu), no hace falta que EmpresaFactura la vuelva a emitir. Envía los mismos datos y añade el objeto external con el número, el QR y el PDF de tu factura. EmpresaFactura:
- la guarda con tu número (sin serie propia) y con tu PDF, con tu diseño. Ese PDF es el que se descarga, se envía por email y sale en el portal del cliente;
- no la registra en VeriFactu ni mueve stock (ya lo hizo tu programa);
- la cuenta en ventas, IVA repercutido, cobros e informes, igual que las demás.
Nunca los dos a la vez. Si tu programa registra la factura en VeriFactu, usa siempre external. Sin él, EmpresaFactura emitiría otra factura de la misma venta y quedaría registrada dos veces.
| Campo | Obligatorio | Descripción |
|---|---|---|
issueDate | Sí | Fecha de tu factura, AAAA-MM-DD. Tiene que ser la del QR. |
external.number | Sí | Número de tu factura (máx. 60), tal como figura en el PDF y en el QR. No puede existir ya en la empresa. |
external.qrUrl | Sí | URL del QR VeriFactu de tu factura (https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=…&numserie=…&fecha=…&importe=…). |
external.pdf | Sí | PDF de tu factura en base64. Máximo 4 MB. |
external.pdfFileName | No | Nombre del archivo. |
Antes de guardar nada se comprueba que el QR es de la sede de la AEAT y que su NIF es el de la empresa de la clave, su número es external.number, su fecha es issueDate y su importe es el total de las líneas. Si algo no cuadra, error 422 y no se registra: así no queda guardada una factura que tu programa no haya registrado o con importes distintos.
{
"externalId": "whmcs:mihosting.es:5821",
"invoiceType": "full",
"issueDate": "2026-10-06",
"customer": { "company": "Pérez Diseño SL", "nif": "B12345678" },
"lines": [ { "description": "Hosting anual", "quantity": 1, "vatRate": 21, "total": 121.00 } ],
"paid": true,
"external": {
"number": "WH-2026-0153",
"qrUrl": "https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=B27000000&numserie=WH-2026-0153&fecha=06-10-2026&importe=121.00",
"pdf": "JVBERi0xLjcKJcfsj6IK…"
}
}
La respuesta es la de siempre, con "externallyIssued": true y "verifactu": null (el registro es de tu programa). Desde la aplicación estas facturas no se pueden anular ni rectificar: se hace en el programa que las emitió.
7. GET/invoices/:id: consultar una factura
Devuelve el objeto factura (sin el campo replayed). Úsalo para saber si ya está cobrada o para comprobar el estado del envío a VeriFactu.
curl https://api.empresafactura.com/api/v1/invoices/0192f3a4-7c1e-7b2d-9a51-6c0e8d4f2a10 \
-H "X-API-Key: TU_CLAVE"
Solo encuentra facturas de la empresa de la clave. Con un id de otra empresa o inexistente, 404.
8. GET/invoices/:id/pdf: descargar el PDF
Devuelve el PDF de la factura con el diseño que tengas configurado en EmpresaFactura, y con el QR de VeriFactu. Si la factura la emitió tu programa (modo copia), devuelve tu PDF original tal cual.
Content-Type: application/pdfContent-Disposition: inline; filename="factura_F2026-000123.pdf"(osimplificada_….pdf)
Si quieres que tu cliente se lo descargue desde tu web, pídelo desde tu servidor y reenvíalo. No pongas la clave en un enlace del navegador.
9. POST/received-invoices: registrar una factura recibida
Registra una factura de compra (la que te envía un proveedor) con sus datos y, si quieres, el PDF original. El PDF se guarda tal cual, con el diseño del proveedor, y se descarga desde la factura en la aplicación. Sirve para conectar programas que reciben o generan facturas de proveedores: un buzón de correo, una plataforma de compras, el software de tu gestoría…
Una factura recibida no se emite ni se envía a VeriFactu: la registró su emisor. Entra en Compras › Facturas recibidas, cuenta para el IVA soportado y, si lleva retención, para los modelos 111/115.
Cuerpo de la petición
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
supplier | objeto | Sí | Datos del proveedor (tabla siguiente). |
invoiceNumber | texto (máx. 60) | Sí | Número de la factura tal como lo pone el proveedor. |
issueDate | fecha ISO 8601 | Sí | Fecha de la factura del proveedor. |
dueDate | fecha ISO 8601 | No | Vencimiento. |
lines | lista | Sí (mín. 1) | Líneas, con el mismo formato que en POST /invoices: vatRate y total (con IVA) o unitPrice (sin IVA). |
total | número | No (recomendado) | Total de la factura del proveedor. Si lo envías, se comprueba que las líneas suman eso; si no cuadra, error 422 y no se registra nada. |
retentionType | "PROFESIONAL" | "ALQUILER" | No | Retención de IRPF que le practicas al proveedor (profesional o alquiler). |
retentionPercentage | número | Con retención | % de la retención, por ejemplo 15. Se resta del total. |
paid | booleano | No | Con true se registra el pago y queda PAID. Si no, queda SENT (pendiente de pago). |
notes | texto (máx. 1000) | No | Observaciones internas. |
pdf | texto base64 | No | El PDF de la factura en base64 (se admite también con el prefijo data:application/pdf;base64,). Máximo 4 MB. |
pdfFileName | texto | No | Nombre del archivo. Por defecto, <invoiceNumber>.pdf. |
Objeto supplier
| Campo | Descripción |
|---|---|
nif | Obligatorio. NIF/CIF del proveedor. Se aceptan espacios, puntos y guiones. |
name | Razón social. Obligatorio si el proveedor aún no existe en tu empresa. |
email, phone, address, postalCode, city, province, country | Datos del proveedor nuevo. |
El proveedor se busca por NIF. Si ya existe se usa tal como está (sus datos no se sobrescriben); si no, se crea con estos datos.
Ejemplo
POST /api/v1/received-invoices
X-API-Key: TU_CLAVE
Content-Type: application/json
{
"supplier": { "nif": "B27000000", "name": "Papelería Lucense SL", "email": "facturas@papeleria.es" },
"invoiceNumber": "FV-2026/0457",
"issueDate": "2026-10-03",
"dueDate": "2026-11-02",
"lines": [
{ "description": "Papel A4 (cajas)", "quantity": 5, "vatRate": 21, "total": 121.00 },
{ "description": "Libros de registro", "quantity": 1, "vatRate": 4, "total": 20.80 }
],
"total": 141.80,
"pdf": "JVBERi0xLjcKJcfsj6IK…"
}
Respuesta
201 Created si la factura es nueva; 200 OK con "replayed": true si ya estaba registrada.
{
"success": true,
"data": {
"id": "6f1d2c3b-9a8e-4f70-b1c2-3d4e5f6a7b8c",
"number": "FV-2026/0457",
"status": "SENT",
"supplier": { "id": "…", "name": "Papelería Lucense SL", "nif": "B27000000" },
"issueDate": "2026-10-03T00:00:00.000Z",
"dueDate": "2026-11-02T00:00:00.000Z",
"subtotal": 120.00,
"totalVat": 21.80,
"retentionAmount": 0,
"total": 141.80,
"hasPdf": true,
"replayed": false
}
}
Idempotencia
Un proveedor no repite número de factura, así que el par NIF del proveedor + invoiceNumber identifica la factura. Reenviarla devuelve la ya registrada sin cambiarla. Si la primera vez no llegó el PDF (o falló al guardarlo), el reenvío con pdf lo adjunta.
Para corregir una factura recibida mal registrada, edítala o bórrala desde la aplicación.
10. El objeto factura
| Campo | Descripción |
|---|---|
id | Identificador de la factura en EmpresaFactura. |
type | F1 = factura completa. F2 = factura simplificada. |
number | Número legal con su serie. Lo asigna EmpresaFactura, de forma correlativa. |
status | Estado (tabla siguiente). |
issueDate | Fecha de expedición (ISO 8601, UTC). |
subtotal | Base imponible, en euros (número con 2 decimales). |
totalVat | Cuota de IVA. |
total | Total de la factura. |
externalId | El identificador de pedido que enviaste. |
source | El programa que la creó (source enviado o nombre de la clave de API). |
payments | Cobros registrados: [{ method, methodName, amount, date, authCode }]. Vacío si no está cobrada. |
verifactu | { status, qrUrl, hash } del registro VeriFactu, o null si aún no existe. |
externallyIssued | true si la emitió y registró tu programa (modo copia): el PDF es el tuyo y verifactu es null. |
replayed | Solo en POST /invoices: true si la factura ya existía. |
Estados de la factura (status)
| Valor | Significado |
|---|---|
SENT | Emitida. Las facturas creadas por la API nacen así. |
PAID | Cobrada (por ejemplo, con paid: true). |
PARTIALLY_PAID | Cobrada en parte (cobros registrados desde la aplicación). |
OVERDUE | Vencida sin cobrar. |
CANCELLED | Anulada o rectificada desde la aplicación. |
Estados del registro VeriFactu (verifactu.status)
| Valor | Significado |
|---|---|
GENERATED | Registro creado, encadenado y con QR, pendiente de envío. Si la empresa trabaja en modo No VeriFactu se queda así: se conserva sin enviarse, como permite la norma. |
SENT | Enviado a la AEAT; aceptado con avisos o pendiente de respuesta definitiva. |
ACCEPTED | Aceptado por la AEAT. |
REJECTED | Rechazado por la AEAT. Lo verás también en la aplicación para corregirlo. |
ERROR | Fallo técnico al enviarlo (conexión, certificado…). Se revisa desde la aplicación. |
El envío a la AEAT se hace en segundo plano y no retrasa la respuesta. La factura es válida desde que se emite: el QR y la huella (hash) ya están en el PDF.
11. Reglas de facturación
- Factura completa o simplificada. Si el comprador quiere factura (empresa o autónomo que la deduce), envía
invoiceType: "full"con su NIF. Si no,"simplified". - Nunca se inventa un NIF. Una factura completa sin NIF válido da error
400; no se emite con un NIF genérico. - Límite de la simplificada: 3.000 € (IVA incluido; art. 4.1 del RD 1619/2012, ventas al por menor). Si la supera, error
422: pide el NIF y emite una completa. - El IVA tiene que existir en la empresa. Si envías
vatRate: 10y la empresa no tiene un IVA del 10 %, error422. No se aplica otro tipo sin avisar. - Numeración. Las completas van en la serie por defecto de facturas; las simplificadas, en su serie de simplificadas (se crea sola, código
SIM, si no existe). No puedes fijar el número, salvo en las facturas que ya emitió tu programa (modo copia), que conservan el suyo. - Importes. Con
total, la base se calcula a partir del total con IVA, de modo que el total de la factura es exactamente lo que cobraste. - Trazabilidad. Cada factura emitida por API deja una nota en su historial: «Factura … emitida por API (pedido …)».
12. Idempotencia y reintentos
externalId identifica el pedido. EmpresaFactura garantiza una sola factura por externalId en cada empresa, aunque lleguen dos peticiones a la vez:
- La primera vez:
201con la factura nueva. - Las siguientes:
200con la misma factura y"replayed": true. El cuerpo nuevo se ignora: no se modifica la factura ya emitida.
Por eso reintentar siempre es seguro. Si se corta la conexión, hay un tiempo de espera o un error 5xx, repite la misma petición con el mismo externalId. No generes un externalId nuevo en cada intento, o podrías facturar dos veces.
Para corregir una factura ya emitida (precio mal, cliente equivocado…) haz una rectificativa desde la aplicación. Una factura legal no se edita.
13. Errores
Los errores devuelven "success": false y un message en español, pensado para mostrarlo o guardarlo en el registro de tu integración.
HTTP/1.1 422 Unprocessable Entity
{ "success": false, "message": "La empresa no tiene configurado un IVA del 10 %" }
| Código | Cuándo | Qué hacer |
|---|---|---|
400 | Datos que faltan o no son válidos: externalId, invoiceType, NIF en factura completa, líneas (cantidad, vatRate, total/unitPrice), issueDate, nombre del cliente nuevo. | Corrige la petición. No la repitas igual. |
401 | Falta la clave, tiene mal formato, no existe o está revocada. | Revisa la clave en API e integraciones. |
403 | El módulo API e integraciones no está activo en la empresa. | Actívalo en la aplicación. |
404 | La factura no existe o es de otra empresa. | Revisa el id y la clave. |
409 | Modo copia: ya hay una factura con ese número en la empresa. | Revisa la numeración de tu programa: no puede repetir números de EmpresaFactura. |
413 | El pdf de una factura recibida pasa de 4 MB. | Envía el PDF comprimido o súbelo desde la aplicación. |
422 | Petición correcta que no se puede registrar: IVA no configurado, simplificada de más de 3.000 €, una factura recibida cuyas líneas no suman su total, o (modo copia) un QR que no es de esa factura. | Configura el IVA, emite una factura completa con NIF o revisa las líneas. |
429 | Demasiadas peticiones. | Espera lo que indique Retry-After y reintenta. |
5xx | Error temporal nuestro. | Reintenta con el mismo externalId, esperando cada vez más (1 s, 5 s, 30 s…). |
Mensajes que puedes recibir, tal cual:
Falta externalId (identificador del pedido en la tienda)externalId demasiado largo (máx. 120)invoiceType debe ser 'full' o 'simplified'Para emitir factura hace falta el NIF del clienteAñade al menos una líneaLínea 2: cantidad no válida·Línea 2: falta vatRate (% de IVA)·Línea 2: falta total o unitPriceissueDate no válidaFalta el nombre del clienteLa empresa no tiene configurado un IVA del 10 %Para más de 3000 € hace falta el NIF del cliente (factura completa)Falta la cabecera X-API-Key·Clave de API con formato no válido·Clave de API no válida o revocadaEl módulo API no está activo en esta empresaFactura no encontrada- Modo copia:
Falta external.number (número de la factura en tu programa)·Con external hace falta issueDate (AAAA-MM-DD), la fecha de la factura·Falta external.pdf (el PDF de la factura en base64)·Falta external.qrUrl (URL del QR VeriFactu de la factura)·external.qrUrl no es un QR VeriFactu de la AEAT·El QR es del NIF … y la empresa es …·El QR es de la factura …, no de …·El QR tiene fecha … y la factura …·El QR dice 121.00 € y las líneas suman 120.99 €·Ya existe una factura con el número … en la empresa - Facturas recibidas:
Falta supplier.nif (NIF del proveedor)·Falta supplier.name (el proveedor no existe todavía)·Falta invoiceNumber (número de la factura del proveedor)·Falta issueDate o no es válida·retentionType debe ser 'PROFESIONAL' o 'ALQUILER'·pdf no es un PDF en base64·pdf demasiado grande (máx. 4 MB)·Las líneas suman 141.80 € y total dice 142.00 €
14. Límites de uso
Hay un límite de peticiones por dirección IP en una ventana de tiempo. Cada respuesta trae las cabeceras x-ratelimit-limit, x-ratelimit-remaining y x-ratelimit-reset; al superarlo recibirás 429 con Retry-After (segundos).
Una tienda que factura sus pedidos uno a uno no se acerca al límite. Para cargas masivas (por ejemplo, facturar un histórico), espacia las peticiones o escríbenos antes.
15. Ejemplos en PHP, Node.js y Python
PHP (cURL)
<?php
$payload = [
'externalId' => 'web:pedido-1001',
'invoiceType' => 'full',
'customer' => ['name' => 'Ana Pérez', 'company' => 'Pérez Diseño SL', 'nif' => 'B12345678'],
'lines' => [['description' => 'Camiseta', 'quantity' => 2, 'vatRate' => 21, 'total' => 30.00]],
'paid' => true,
];
$ch = curl_init('https://api.empresafactura.com/api/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('EFACTURA_API_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status === 200 || $status === 201) {
echo 'Factura ' . $body['data']['number'] . ' (id ' . $body['data']['id'] . ")\n";
} else {
echo "Error $status: " . ($body['message'] ?? 'sin respuesta') . "\n";
}
Node.js (18 o superior)
import { writeFile } from 'node:fs/promises';
const API = 'https://api.empresafactura.com/api/v1';
const headers = { 'X-API-Key': process.env.EFACTURA_API_KEY, 'Content-Type': 'application/json' };
const res = await fetch(`${API}/invoices`, {
method: 'POST',
headers,
body: JSON.stringify({
externalId: 'web:pedido-1001',
invoiceType: 'simplified',
customer: { name: 'Ana Pérez' },
lines: [{ description: 'Camiseta', quantity: 2, vatRate: 21, total: 30.0 }],
}),
});
const body = await res.json();
if (!res.ok) throw new Error(`${res.status}: ${body.message}`);
console.log(body.data.number, body.data.replayed ? '(ya existía)' : '(nueva)');
// PDF
const pdf = await fetch(`${API}/invoices/${body.data.id}/pdf`, { headers });
await writeFile('factura.pdf', Buffer.from(await pdf.arrayBuffer()));
Python (requests)
import os, requests
API = "https://api.empresafactura.com/api/v1"
headers = {"X-API-Key": os.environ["EFACTURA_API_KEY"]}
r = requests.post(f"{API}/invoices", headers=headers, timeout=30, json={
"externalId": "web:pedido-1001",
"invoiceType": "full",
"customer": {"name": "Ana Pérez", "company": "Pérez Diseño SL", "nif": "B12345678"},
"lines": [{"description": "Camiseta", "quantity": 2, "vatRate": 21, "total": 30.00}],
"paid": True,
})
body = r.json()
if not r.ok:
raise RuntimeError(f"{r.status_code}: {body['message']}")
invoice = body["data"]
pdf = requests.get(f"{API}/invoices/{invoice['id']}/pdf", headers=headers, timeout=30)
open(f"{invoice['number']}.pdf", "wb").write(pdf.content)
16. Plugins: WooCommerce, PrestaShop y WHMCS
Si usas una de estas plataformas no necesitas programar nada: el plugin envía cada pedido a la API y EmpresaFactura emite la factura legal. Pídelos a soporte. En los tres:
- En el registro y el checkout el comprador elige Factura simplificada o Factura (con NIF); con factura, el NIF es obligatorio.
- El
externalIdlleva la plataforma, el dominio y el número de pedido, así que reenviar un pedido nunca lo factura dos veces. - Si algo falla, el pedido muestra el error y un botón para volver a emitir.
Los tres tienen el ajuste Factura legal:
- EmpresaFactura la emite (por defecto): la tienda manda el pedido y EmpresaFactura pone número, VeriFactu, QR y PDF.
- La tienda ya la emite (copia): para tiendas con su propio módulo VeriFactu. El plugin manda el número, la fecha, el QR y el PDF de la factura de la tienda, y EmpresaFactura guarda una copia sin volver a registrarla (ver modo copia). Indica dónde guarda tu módulo VeriFactu la URL del QR: en WHMCS y PrestaShop,
tabla:columna_id:columna_qr(o un hookEfacturaVerifactuQr/actionEfacturaVerifactuQr); en WooCommerce, el metadato del pedido. En WooCommerce el PDF sale del plugin «PDF Invoices & Packing Slips» o del filtroefactura_external_invoice.
WooCommerce
- WordPress › Plugins › Añadir nuevo › Subir plugin con el
efactura.zipy actívalo. - WooCommerce › Ajustes › EmpresaFactura: pega la clave y elige cuándo facturar (pedido Completado o Procesando).
- El pedido recibe una nota con el número de factura y el cliente ve «Descargar factura» en Mi cuenta.
Funciona con el checkout clásico y con el de bloques. Si desactivas la elección de documento, se usa el NIF que recoja otro plugin (_billing_nif, _billing_vat…) o, sin él, simplificada.
PrestaShop (1.7.5 o superior, y 8)
- Módulos › Subir un módulo con
efactura.zip› Configurar: pega la clave. - Pedidos › Facturas: desactiva «Activar facturas». La factura legal es la de EmpresaFactura; si no, el cliente recibe dos. (En modo copia, al revés: déjalas activas, porque la legal es la de PrestaShop.)
- Al pasar un pedido a un estado pagado se emite la factura. El cliente la descarga en el detalle del pedido.
Los descuentos del carrito se reparten en proporción entre las líneas, conservando el IVA de cada una, y el total cuadra al céntimo con lo cobrado.
WHMCS
- Copia la carpeta
modulessobre la raíz de WHMCS › Ajustes del sistema › Módulos addon › EmpresaFactura › Activar. Pega la clave y da acceso al rol de administrador. - Activa el campo Tax ID (Ajustes › Impuestos): es el NIF.
- Al pagarse una factura de WHMCS se emite la legal en EmpresaFactura; la de WHMCS queda como documento interno, con el número y el enlace de descarga en sus notas.
Con impuestos «Inclusive» se envía el importe con IVA; con «Exclusive», la base (puede haber 1 céntimo de diferencia con el redondeo de WHMCS).
17. Qué no hace la API
La API v1 está pensada para facturar ventas y registrar facturas de proveedores. Esto se hace desde la aplicación:
- Rectificar o anular facturas.
- Listar o buscar facturas, clientes o productos.
- Presupuestos, albaranes y pedidos.
- Consultar, editar o borrar facturas recibidas.
- Avisos automáticos (webhooks). Para conocer el estado de VeriFactu, consulta
GET /invoices/:id.
Si necesitas alguna de estas funciones en la API, cuéntanos tu caso.
18. Buenas prácticas y seguridad
- La clave es como una contraseña: permite emitir facturas legales en tu nombre. Guárdala en variables de entorno o en la configuración del servidor, nunca en el código de la web, en el navegador ni en un repositorio.
- Una clave por integración. Si una tienda se ve comprometida, revocas solo esa.
- Usa un
externalIdestable y que no se repita entre tiendas:origen:dominio:pedido. - Guarda el
idy elnumberde la factura junto al pedido. - Factura cuando el pedido esté cobrado, no al crearlo: así no hace falta anular facturas de pedidos abandonados.
- Pon un tiempo de espera (30 s es suficiente) y reintenta los fallos de red y
5xxcon el mismoexternalId. - Solo HTTPS. Las peticiones por HTTP no se aceptan.
¿Dudas con la integración? Escríbenos o llama al 982 87 38 91.