API v1 · Integraciones

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

URL basehttps://api.empresafactura.com/api/v1
FormatoJSON en UTF-8. Envía Content-Type: application/json en las peticiones con cuerpo.
AutenticaciónCabecera X-API-Key con una clave de tu empresa.
UsoDe servidor a servidor. La API no admite llamadas desde el navegador (CORS cerrado) para que tu clave no quede expuesta.
Versiónv1. 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 rutaPara qué sirve
GET/whoamiComprueba la clave y devuelve la empresa a la que pertenece.
POST/invoicesEmite una factura completa o simplificada a partir de un pedido.
GET/invoices/:idDevuelve la factura con su estado y su registro VeriFactu (QR).
GET/invoices/:id/pdfDescarga el PDF de la factura.
POST/received-invoicesRegistra 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

  1. Entra en app.empresafactura.com y abre el menú API e integraciones.
  2. Pulsa Añadir y ponle un nombre que diga dónde se usa (por ejemplo, «WooCommerce tienda.es»).
  3. 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

CampoTipoObligatorioDescripción
externalIdtexto (máx. 120)SíIdentificador único del pedido en tu sistema. Recomendado: origen:dominio:número, por ejemplo woo:mitienda.es:1234.
sourcetexto (máx. 60)NoNombre 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"Nofull: 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.
customerobjetoEn fullDatos del comprador (tabla siguiente).
lineslistaSí (mín. 1)Líneas de la factura (ver más abajo).
issueDatefecha ISO 8601NoFecha de expedición, por ejemplo "2026-10-06". Por defecto, el momento de la petición.
notestexto (máx. 1000)NoObservaciones que aparecen en la factura, por ejemplo "Pedido web #1234".
paidbooleanoNoSolo 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.
paymentslistaNoCobros 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.

CampoTipoObligatorioDescripción
methodtextoSí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.
authCodetexto (máx. 120)NoNº de autorización de la tarjeta o de Bizum, id de la transacción o referencia de la transferencia.
dataobjeto JSON (máx. 8 KB)NoLog 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.
amountnúmeroSi hay variosImporte cobrado. Con un solo cobro, por defecto, el total de la factura. Si hay varios cobros (pago mixto), es obligatorio en todos.
datefecha ISO 8601NoMomento 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

CampoDescripción
nifNIF/CIF/NIE del comprador. Obligatorio en full. Se aceptan espacios, puntos y guiones ("b-12.345.678" → B12345678).
nameNombre de la persona. En las simplificadas es el único dato que se guarda (como referencia).
companyRazón social. Si viene, es el nombre fiscal del cliente; si no, se usa name.
email, phoneContacto.
address, postalCode, city, provinceDirección fiscal.
countryPaí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[])

CampoTipoDescripción
descriptiontexto (máx. 500)Concepto. Si va vacío: «Artículo».
quantitynúmero > 0Unidades. Por defecto, 1.
vatRatenúmeroObligatorio. % de IVA: 21, 10, 4, 0… Tiene que existir en los IVA de la empresa.
totalnúmeroImporte total de la línea con IVA (lo que se cobró). Se respeta al céntimo. Es la opción recomendada.
unitPricenúmeroAlternativa 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.
CampoObligatorioDescripción
issueDateSíFecha de tu factura, AAAA-MM-DD. Tiene que ser la del QR.
external.numberSí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.qrUrlSíURL del QR VeriFactu de tu factura (https://www2.agenciatributaria.gob.es/wlpl/TIKE-CONT/ValidarQR?nif=…&numserie=…&fecha=…&importe=…).
external.pdfSíPDF de tu factura en base64. Máximo 4 MB.
external.pdfFileNameNoNombre 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/pdf
  • Content-Disposition: inline; filename="factura_F2026-000123.pdf" (o simplificada_….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

CampoTipoObligatorioDescripción
supplierobjetoSíDatos del proveedor (tabla siguiente).
invoiceNumbertexto (máx. 60)SíNúmero de la factura tal como lo pone el proveedor.
issueDatefecha ISO 8601SíFecha de la factura del proveedor.
dueDatefecha ISO 8601NoVencimiento.
lineslistaSí (mín. 1)Líneas, con el mismo formato que en POST /invoices: vatRate y total (con IVA) o unitPrice (sin IVA).
totalnúmeroNo (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"NoRetención de IRPF que le practicas al proveedor (profesional o alquiler).
retentionPercentagenúmeroCon retención% de la retención, por ejemplo 15. Se resta del total.
paidbooleanoNoCon true se registra el pago y queda PAID. Si no, queda SENT (pendiente de pago).
notestexto (máx. 1000)NoObservaciones internas.
pdftexto base64NoEl PDF de la factura en base64 (se admite también con el prefijo data:application/pdf;base64,). Máximo 4 MB.
pdfFileNametextoNoNombre del archivo. Por defecto, <invoiceNumber>.pdf.

Objeto supplier

CampoDescripción
nifObligatorio. NIF/CIF del proveedor. Se aceptan espacios, puntos y guiones.
nameRazón social. Obligatorio si el proveedor aún no existe en tu empresa.
email, phone, address, postalCode, city, province, countryDatos 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

CampoDescripción
idIdentificador de la factura en EmpresaFactura.
typeF1 = factura completa. F2 = factura simplificada.
numberNúmero legal con su serie. Lo asigna EmpresaFactura, de forma correlativa.
statusEstado (tabla siguiente).
issueDateFecha de expedición (ISO 8601, UTC).
subtotalBase imponible, en euros (número con 2 decimales).
totalVatCuota de IVA.
totalTotal de la factura.
externalIdEl identificador de pedido que enviaste.
sourceEl programa que la creó (source enviado o nombre de la clave de API).
paymentsCobros 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.
externallyIssuedtrue si la emitió y registró tu programa (modo copia): el PDF es el tuyo y verifactu es null.
replayedSolo en POST /invoices: true si la factura ya existía.

Estados de la factura (status)

ValorSignificado
SENTEmitida. Las facturas creadas por la API nacen así.
PAIDCobrada (por ejemplo, con paid: true).
PARTIALLY_PAIDCobrada en parte (cobros registrados desde la aplicación).
OVERDUEVencida sin cobrar.
CANCELLEDAnulada o rectificada desde la aplicación.

Estados del registro VeriFactu (verifactu.status)

ValorSignificado
GENERATEDRegistro 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.
SENTEnviado a la AEAT; aceptado con avisos o pendiente de respuesta definitiva.
ACCEPTEDAceptado por la AEAT.
REJECTEDRechazado por la AEAT. Lo verás también en la aplicación para corregirlo.
ERRORFallo 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: 10 y la empresa no tiene un IVA del 10 %, error 422. 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: 201 con la factura nueva.
  • Las siguientes: 200 con 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ódigoCuándoQué hacer
400Datos 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.
401Falta la clave, tiene mal formato, no existe o está revocada.Revisa la clave en API e integraciones.
403El módulo API e integraciones no está activo en la empresa.Actívalo en la aplicación.
404La factura no existe o es de otra empresa.Revisa el id y la clave.
409Modo 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.
413El pdf de una factura recibida pasa de 4 MB.Envía el PDF comprimido o súbelo desde la aplicación.
422Petició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.
429Demasiadas peticiones.Espera lo que indique Retry-After y reintenta.
5xxError 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 cliente
  • Añade al menos una línea
  • Línea 2: cantidad no válida · Línea 2: falta vatRate (% de IVA) · Línea 2: falta total o unitPrice
  • issueDate no válida
  • Falta el nombre del cliente
  • La 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 revocada
  • El módulo API no está activo en esta empresa
  • Factura 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 externalId lleva 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 hook EfacturaVerifactuQr / actionEfacturaVerifactuQr); en WooCommerce, el metadato del pedido. En WooCommerce el PDF sale del plugin «PDF Invoices & Packing Slips» o del filtro efactura_external_invoice.

WooCommerce

  1. WordPress › Plugins › Añadir nuevo › Subir plugin con el efactura.zip y actívalo.
  2. WooCommerce › Ajustes › EmpresaFactura: pega la clave y elige cuándo facturar (pedido Completado o Procesando).
  3. 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)

  1. Módulos › Subir un módulo con efactura.zip › Configurar: pega la clave.
  2. 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.)
  3. 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

  1. Copia la carpeta modules sobre la raíz de WHMCS › Ajustes del sistema › Módulos addon › EmpresaFactura › Activar. Pega la clave y da acceso al rol de administrador.
  2. Activa el campo Tax ID (Ajustes › Impuestos): es el NIF.
  3. 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 externalId estable y que no se repita entre tiendas: origen:dominio:pedido.
  • Guarda el id y el number de 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 5xx con el mismo externalId.
  • Solo HTTPS. Las peticiones por HTTP no se aceptan.

¿Dudas con la integración? Escríbenos o llama al 982 87 38 91.

¿Listo para empezar?

Prueba EmpresaFactura gratis o habla con nuestro equipo. Sin compromiso.