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 cuatro:

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.

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.
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.

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",
  "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",
  "paid": true
}

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",
    "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.

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.

  • 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. 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.
verifactu{ status, qrUrl, hash } del registro VeriFactu, o null si aún no existe.
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.

10. 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.
  • 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 …)».

11. 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.

12. 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.
422Petición correcta que no se puede facturar: IVA no configurado, o simplificada de más de 3.000 €.Configura el IVA o emite una factura completa con NIF.
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

13. 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.

14. 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)

15. 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.

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.
  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).

16. Qué no hace la API

La API v1 está pensada para facturar ventas. Esto se hace desde la aplicación:

  • Rectificar o anular facturas.
  • Listar o buscar facturas, clientes o productos.
  • Presupuestos, albaranes, pedidos y 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.

17. 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.