API Reference

API REST para extracción automática de facturas españolas. PDF → Facturae 3.2.2 firmado con XAdES-BES en una sola llamada.

Base URL https://api.facturax.app
Versión v1.0.0
Autenticación Bearer JWT · X-API-Key
Formato JSON · multipart/form-data
Endpoint principal

POST /convert

El endpoint más importante de la API. Acepta un PDF o un JSON con los datos de la factura y devuelve el Facturae XML 3.2.2 firmado con XAdES-BES, junto con los datos estructurados extraídos.

MODO 1 — PDF → XML
Envías el PDF de la factura. La IA extrae los datos y genera el XML firmado. Ideal si recibes facturas en PDF de tus proveedores.
MODO 2 — JSON → XML
Tu software ya tiene los datos. Envías un JSON y obtienes el XML firmado. Sin OCR, sin extracción — pura conversión de formato.
POST /convert multipart/form-data
CampoTipoDescripción
file requerido File PDF, JPG o PNG de la factura (máx. 20MB)
Ejemplo — cURL
curl -X POST https://api.facturax.app/convert \
  -H "X-API-Key: fct_tu_api_key" \
  -F "file=@factura.pdf"
Ejemplo — Python
import requests

response = requests.post(
    "https://api.facturax.app/convert",
    headers={"X-API-Key": "fct_tu_api_key"},
    files={"file": open("factura.pdf", "rb")}
)
data = response.json()
# Guardar el XML firmado
with open(data["filename"], "w") as f:
    f.write(data["xml"])
POST /convert application/json
Referencia de campos — Emisor
Campo Tipo Requerido Descripción
vendor_namestringrequeridoNombre o razón social del emisor
vendor_vatstringrequeridoCIF, NIF o NIE del emisor. Ej: B12345678
vendor_addressstringopcionalDirección completa del emisor
vendor_citystringopcionalCiudad del emisor
vendor_postal_codestringopcionalCódigo postal (5 dígitos). Ej: 28001
vendor_provincestringopcionalProvincia del emisor
vendor_countrystringopcionalCódigo ISO 3166-1 alfa-3. Por defecto: ESP
vendor_emailstringopcionalEmail de contacto del emisor
Receptor
Campo Tipo Requerido Descripción
buyer_namestringrequeridoNombre o razón social del receptor
buyer_vatstringrequeridoCIF, NIF o NIE del receptor
buyer_addressstringopcionalDirección del receptor
buyer_citystringopcionalCiudad del receptor
buyer_postal_codestringopcionalCódigo postal del receptor
Factura
Campo Tipo Requerido Descripción
invoice_numberstringrequeridoNúmero de factura. Ej: F2024-001
invoice_datestringrequeridoFecha emisión formato YYYY-MM-DD. Ej: 2024-06-15
totalnumberrequeridoImporte total en euros (IVA incluido)
subtotalnumberopcionalBase imponible sin IVA
taxnumberopcionalCuota de IVA en euros
declared_tax_ratenumberopcionalTipo de IVA principal en %. Valores: 21 · 10 · 4 · 0
irpf_ratenumberopcionalPorcentaje de retención IRPF. Ej: 15
irpf_amountnumberopcionalImporte retención IRPF (negativo). Ej: -15.00
due_datestringopcionalFecha de vencimiento formato YYYY-MM-DD
currencystringopcionalCódigo ISO 4217. Por defecto: EUR
ibanstringopcionalIBAN del emisor. Ej: ES21 0049 0001 5021 0001 3303
payment_methodstringopcionalValores: transferencia · domiciliacion · cheque · efectivo · tarjeta
Líneas — line_items[]
Array opcional pero recomendado. Si se omite, el XML se genera con una línea genérica a partir de los importes de cabecera. Incluirlo mejora la validación en FACe y la trazabilidad contable.
Campo Tipo Requerido Descripción
descriptionstringrequeridoDescripción del concepto
quantitynumberrequeridoCantidad. Acepta decimales. Ej: 1, 2.5
unit_pricenumberrequeridoPrecio unitario sin IVA
totalnumberrequeridoTotal de la línea sin IVA (unit_price × quantity)
tax_ratenumberopcional% IVA de esta línea. Ej: 21 · 10 · 4 · 0
Ejemplo completo
{
  // Emisor
  "vendor_name":        "Servicios Tech S.L.",
  "vendor_vat":         "B12345678",
  "vendor_address":     "Calle Mayor 1, 2º",
  "vendor_city":        "Madrid",
  "vendor_postal_code": "28001",
  "vendor_province":    "Madrid",
  "vendor_country":     "ESP",

  // Receptor
  "buyer_name":         "Ayuntamiento de Alcobendas",
  "buyer_vat":          "P2800200B",
  "buyer_address":      "Plaza Mayor 1",
  "buyer_city":         "Alcobendas",
  "buyer_postal_code":  "28100",

  // Factura
  "invoice_number":     "F2024-001",
  "invoice_date":       "2024-06-15",
  "due_date":           "2024-07-15",
  "currency":           "EUR",
  "payment_method":     "transferencia",
  "iban":               "ES21 0049 0001 5021 0001 3303",

  // Importes
  "subtotal":           1000.00,
  "declared_tax_rate":  21,
  "tax":                210.00,
  "total":              1210.00,
  "irpf_rate":          15,
  "irpf_amount":        -150.00,

  // Líneas
  "line_items": [
    {
      "description": "Desarrollo módulo de facturación",
      "quantity":    10,
      "unit_price":  100.00,
      "total":       1000.00,
      "tax_rate":    21
    }
  ]
}
Ejemplo mínimo — solo obligatorios
{
  "vendor_name":    "Tech S.L.",
  "vendor_vat":     "B12345678",
  "buyer_name":     "Ayuntamiento de Alcobendas",
  "buyer_vat":      "P2800200B",
  "invoice_number": "F2024-001",
  "invoice_date":   "2024-06-15",
  "total":          121.00
}
cURL
curl -X POST https://api.facturax.app/convert \
  -H "X-API-Key: fct_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"vendor_name":"Tech S.L.","vendor_vat":"B12345678","buyer_name":"Ayto Alcobendas","buyer_vat":"P2800200B","invoice_number":"F-001","invoice_date":"2024-06-15","total":121.0}'
Respuesta (ambos modos)
{
  "xml":        "<?xml version=\"1.0\"...>",   // XML completo como string
  "xml_base64": "PD94bWwg...",             // XML en base64 para binario
  "signed":     true,                      // true si lleva firma XAdES-BES
  "source":     "pdf",                     // "pdf" o "json"
  "invoice": {
    "vendor_name":   "Iberdrola S.A.U.",
    "vendor_vat":    "A95758389",
    "invoice_number":"F2024-001",
    "invoice_date":  "2024-06-15",
    "subtotal":      100.00,
    "tax":           21.00,
    "total":         121.00,
    "iban":          "ES21 0049...",
    "accounting":   { "account_code": "628", "account_label": "Suministros" }
  },
  "warnings":   [],
  "confidence": 0.97,                   // Solo en modo PDF
  "filename":   "facturae_F2024-001.xml",
  "meta": {
    "version":   "3.2.2",
    "standard":  "Facturae",
    "signature": "XAdES-BES"
  }
}
FIRMA CON CERTIFICADO PROPIO

Si tu software gestiona los certificados de tus clientes, puedes pasarlos directamente en la llamada. El certificado no se almacena en nuestros servidores — se usa y descarta.

import base64, requests

with open("certificado_cliente.p12", "rb") as f:
    cert_b64 = base64.b64encode(f.read()).decode()

response = requests.post(
    "https://api.facturax.app/convert",
    headers={
        "X-API-Key":      "fct_tu_api_key",
        "X-P12-B64":      cert_b64,
        "X-P12-Password": "contraseña_certificado",
    },
    files={"file": open("factura.pdf", "rb")}
)

⚡ Quick Start — 5 minutos

De cero a tu primera factura extraída en menos de 5 minutos. Sin configuración, sin OAuth, sin complicaciones.

El camino más rápido: importa la colección
Todas las llamadas preconfiguradas con ejemplos reales. Solo pega tu API Key y ejecuta.
Descargar colección Postman
Postman → Import → Upload File
1
Crea tu cuenta
Regístrate gratis → Dashboard → API Keys → Nueva key
2
Genera tu primera factura firmada
Llama a /convert con el JSON de tu factura — recibirás el XML firmado con XAdES-BES
3
A producción
El XML firmado está listo para subir directamente a FACe
Paso 1: Regístrate en facturax.app/auth y obtén tu API key en el Dashboard → API Keys.

Paso 2: Haz tu primera llamada. El plan gratuito incluye 2 facturas sin tarjeta.
CURL — extracción básica
# Extrae todos los datos de una factura PDF
curl -X POST https://api.facturax.app/extract \
  -H "X-API-Key: fct_tu_api_key" \
  -F "file=@factura.pdf"
PYTHON
import requests

API_KEY = "fct_tu_api_key"

with open("factura.pdf", "rb") as f:
    res = requests.post(
        "https://api.facturax.app/extract",
        headers={"X-API-Key": API_KEY},
        files={"file": ("factura.pdf", f, "application/pdf")},
    )

data = res.json()
print(data["vendor_name"], data["total"], data["confidence_score"])
NODE.JS
import fs from 'fs';
import FormData from 'form-data';
import fetch from 'node-fetch';

const fd = new FormData();
fd.append('file', fs.createReadStream('factura.pdf'), 'factura.pdf');

const res = await fetch('https://api.facturax.app/extract', {
  method: 'POST',
  headers: { 'X-API-Key': 'fct_tu_api_key', ...fd.getHeaders() },
  body: fd,
});
const data = await res.json();
console.log(data.vendor_name, data.total);

Flujo completo: PDF → Facturae firmado

PYTHON — PDF → Facturae XAdES-BES en 2 pasos
import requests

API_KEY = "fct_tu_api_key"
BASE    = "https://api.facturax.app"
HEADERS = {"X-API-Key": API_KEY}

# Paso 1 — Extraer datos del PDF
with open("factura.pdf", "rb") as f:
    r1 = requests.post(f"{BASE}/extract", headers=HEADERS,
                        files={"file": ("factura.pdf", f, "application/pdf")})
r1.raise_for_status()
invoice_data = r1.json()

# Comprobar confianza antes de continuar
if invoice_data["confidence_score"] < 0.7:
    print("Confianza baja — revisar manualmente", invoice_data["warnings"])

# Paso 2 — Generar y descargar Facturae XML firmado
r2 = requests.post(f"{BASE}/convert-to-facturae/download",
                    headers={**HEADERS, "Content-Type": "application/json"},
                    json={"invoice_data": invoice_data, "sign": True})
r2.raise_for_status()

with open("factura_firmada.xml", "wb") as out:
    out.write(r2.content)

print("✓ Facturae firmado guardado en factura_firmada.xml")

Códigos de error

Todos los errores devuelven JSON con error_code (legible por máquina) y detail (legible por humano). Usa error_code para la lógica de reintento — nunca parsees detail.

JSON — Formato de error
{
  "error_code": "QUOTA_EXCEEDED",   // código máquina-legible — estable entre versiones
  "detail":     "Cuota agotada. 150/150 usadas este mes."  // mensaje humano — puede cambiar
}
PYTHON — Lógica de reintento
RETRYABLE = {"EXTRACTION_FAILED", "SERVICE_ERROR", "XML_BUILD_FAILED"}

def handle_error(response):
    err = response.json()
    code = err.get("error_code", "UNKNOWN")

    if code in RETRYABLE:
        retry_with_backoff()          # error transitorio — reintentar
    elif code == "QUOTA_EXCEEDED":
        alert_team("Añadir créditos")  # no reintentar — acción humana
    elif code == "RATE_LIMIT_EXCEEDED":
        wait = response.headers.get("Retry-After", 60)
        time.sleep(int(wait))           # esperar Retry-After y reintentar
    elif code in {"AUTH_INVALID_KEY", "AUTH_REQUIRED"}:
        raise ConfigError("Revisar API key") # nunca reintentar

Catálogo completo de error_code

error_codeHTTP¿Reintentar?Causa y acción
Autenticación
AUTH_REQUIRED401NoFalta la API key o el JWT. Añade X-API-Key o Authorization: Bearer.
AUTH_INVALID_KEY401NoAPI key incorrecta, revocada o inexistente. Verifica en el dashboard.
AUTH_INVALID_TOKEN401NoJWT expirado o manipulado. Re-autentícate con POST /auth/login.
ACCESS_DENIED403NoEmail no verificado, cuenta suspendida o recurso de otro usuario.
Cuota y rate limit
QUOTA_EXCEEDED429NoPlan mensual agotado o créditos API a cero. Añade créditos o espera al reset mensual.
RATE_LIMIT_EXCEEDED429Demasiadas peticiones por minuto. Espera el valor de Retry-After y reintenta.
PLAN_REQUIRED403NoLa función requiere un plan superior (ej: webhooks en Pro, subaccounts en Business).
BATCH_NOT_AVAILABLE403NoEl lote requiere plan Starter o superior.
BATCH_LIMIT_EXCEEDED400NoEl lote supera el máximo de archivos permitido por el plan.
Archivo
FILE_TYPE_INVALID400NoFormato no soportado. Acepta PDF, PNG, JPG y WebP.
FILE_CONTENT_MISMATCH400NoLa extensión no coincide con el contenido real del archivo (magic bytes). Verifica que el archivo no está corrupto o renombrado.
FILE_TOO_LARGE413NoArchivo superior a 20 MB. Comprime o divide antes de subir.
Extracción y generación XML
EXTRACTION_FAILED500Error transitorio en el OCR o en GPT. Reintenta con backoff exponencial (máx 3 veces).
XML_BUILD_FAILED500Error construyendo el XML Facturae. Transitorio — reintenta. Si persiste, contacta soporte.
XML_SIGN_FAILED500NoError firmando con XAdES-BES. Verifica que el certificado .p12 es válido y la contraseña correcta.
SERVICE_ERROR500Error interno genérico. Siempre transitorio — reintenta. Si persiste más de 5 min, consulta status.facturax.app.
Certificado
CERT_INVALID_FORMAT400NoEl archivo no es un .p12/.pfx válido.
CERT_INVALID_PASSWORD400NoContraseña del .p12 incorrecta.
CERT_TOO_LARGE400NoCertificado superior a 5 MB.
CERT_NOT_FOUND404NoEl certificate_id no existe o pertenece a otro usuario.
Recursos
INVOICE_NOT_FOUND404NoEl log_id no existe o fue creado con otra API key.
JOB_NOT_FOUND404NoEl job_id del lote no existe o ya expiró (TTL 24h).
WEBHOOK_NOT_FOUND404NoEl webhook no existe o pertenece a otro usuario.
KEY_NOT_FOUND404NoLa API key no existe o ya fue revocada.
Input inválido
INVALID_INPUT400NoParámetro obligatorio ausente o formato incorrecto. El campo detail especifica cuál.
INVALID_EMAIL400NoEmail con formato inválido en el registro.
DISPOSABLE_EMAIL400NoEl dominio del email es temporal o desechable.
INVALID_PLAN400NoPlan desconocido en el checkout. Valores válidos: starter, pro.
WEBHOOK_INVALID_EVENT400NoEvento de webhook desconocido. Válidos: invoice.extracted, invoice.signed, quota.warning, *.
WEBHOOK_INVALID_URL400NoLa URL del webhook no es HTTPS o no responde a un OPTIONS.
Pagos
STRIPE_NOT_CONFIGURED503El sistema de pagos está temporalmente no disponible. Reintenta en unos minutos.
STRIPE_ERROR500Error en la pasarela de pago. Transitorio — reintenta o contacta soporte.
Cabeceras de cuota: Cada respuesta de /extract incluye X-Quota-Used, X-Quota-Limit y X-Quota-Remaining para monitorizar el consumo sin llamadas adicionales.

Rate limit: 60 req/min por API key (independiente de la IP). La cabecera Retry-After indica los segundos de espera. La cabecera X-RateLimit-Plan indica el plan que está limitando.

Idempotency: Añade X-Idempotency-Key: <uuid> para reintentar sin duplicar consumo. Durante 24h, la misma key devuelve la respuesta cacheada sin consumir cuota. En replay, la respuesta incluye X-Idempotency-Replayed: true.

500 sin error_code: Si recibes un 500 sin campo error_code, es un error no anticipado. Trata estos como SERVICE_ERROR (reintenta hasta 3 veces con backoff) y reporta a soporte@facturax.app con el X-Request-ID de la cabecera de respuesta.

Rate limits

Las peticiones se limitan por API key, no por IP. Todos los planes autenticados comparten el mismo límite base. Las peticiones OPTIONS no cuentan.

Plan Límite Ventana Notas
Free / Starter / Pro / Business60 req/minPor API keyPor cada API key independientemente
Sin autenticar (IPs)10 req/minPor IPSolo para /status y endpoints públicos
⚡ INTEGRACIONES ERP — RECOMENDACIÓN IMPORTANTE

Si mandas facturas en batch (decenas o cientos de golpe), añade un delay de 1 segundo entre llamadas o usa el endpoint POST /batch que procesa múltiples facturas en una sola petición y no tiene este límite por factura. Sin delay, las peticiones que superen 60/min recibirán un 429 con el header Retry-After.

Cabeceras de respuesta relevantes
Cabecera Descripción
Retry-AfterSegundos hasta que se puede reintentar. Solo presente en respuestas 429.
X-Quota-UsedFacturas consumidas en el periodo actual.
X-Quota-LimitLímite total de facturas del plan.
X-Quota-RemainingFacturas restantes antes de agotar la cuota.
Manejo correcto de rate limiting en Python
import requests, time

def convert_with_retry(invoice_data, api_key, max_retries=3):
    headers = {"X-API-Key": api_key, "Content-Type": "application/json"}
    for attempt in range(max_retries):
        r = requests.post("https://api.facturax.app/convert",
                          headers=headers, json=invoice_data)
        if r.status_code == 429:
            wait = int(r.headers.get("Retry-After", 60))
            print(f"Rate limit — esperando {wait}s")
            time.sleep(wait)
            continue
        r.raise_for_status()
        return r.json()
    raise Exception("Rate limit persistente tras 3 reintentos")

# Para batches: procesar con delay entre llamadas
facturas = [...]  # lista de dicts con datos de factura
for factura in facturas:
    resultado = convert_with_retry(factura, "fct_tu_api_key")
    time.sleep(1)  # 1s entre llamadas → máx 60/min, nunca toca el límite
💡 ALTERNATIVA PARA VOLUMEN ALTO

Si necesitas procesar más de 60 facturas/minuto de forma sostenida, contacta en soporte@facturax.app — los planes Business tienen límites personalizados.

Casos de uso

Flujos reales de integración para los casos más habituales.

1. Gestoría — procesamiento automático de emails

Recibir facturas por email, subirlas a FacturaX, obtener el JSON y el Facturae firmado, y registrarlas en el software contable.

PYTHON — pipeline de gestoría
import requests

def procesar_factura(pdf_path: str) -> dict:
    # 1. Extraer datos
    with open(pdf_path, "rb") as f:
        res = requests.post(
            "https://api.facturax.app/extract",
            headers={"X-API-Key": API_KEY},
            files={"file": (pdf_path, f, "application/pdf")}
        )
    data = res.json()

    # 2. Alertar si confianza baja o fraude detectado
    if data.get("confidence_score", 1) < 0.75:
        alert_revisar(pdf_path, data["warnings"])

    fraud = data.get("fraud_checks", {})
    if fraud.get("iban_check", {}).get("detected"):
        alert_fraude_iban(data["vendor_name"], data["iban"])

    # 3. Cuenta PGC asignada automáticamente
    cuenta = data["accounting"]["account_code"]   # ej: "628"

    # 4. Generar Facturae firmado si es para AAPP
    if es_para_administracion(data["buyer_vat"]):
        r2 = requests.post(
            "https://api.facturax.app/convert-to-facturae/download",
            headers={"X-API-Key": API_KEY},
            json={"invoice_data": data, "sign": True}
        )
        with open(pdf_path.replace(".pdf", ".xml"), "wb") as out:
            out.write(r2.content)

    return data

2. Procesamiento masivo con batch jobs (Starter+)

PYTHON — batch de 50 facturas
import requests, time, glob

files   = glob.glob("facturas/*.pdf")[:50]
handles = [("files", (open(f, "rb"))) for f in files]

# Crear job en background — responde inmediatamente
res = requests.post("https://api.facturax.app/batch/job",
                    headers={"X-API-Key": API_KEY},
                    files=handles)
job_id = res.json()["job_id"]

# Polling hasta completar
while True:
    status = requests.get(f"https://api.facturax.app/batch/job/{job_id}",
                          headers={"X-API-Key": API_KEY}).json()
    print(f"{status['processed']}/{status['total']} procesadas")
    if status["status"] == "done": break
    time.sleep(3)

3. Webhook — notificación en tiempo real

PYTHON — receptor de webhook (Flask)
from flask import Flask, request
import hmac, hashlib

app = Flask(__name__)
WEBHOOK_SECRET = "tu_webhook_secret"

@app.route("/webhook", methods=["POST"])
def webhook():
    sig      = request.headers.get("X-Factura-Signature", "")
    expected = "sha256=" + hmac.new(
        WEBHOOK_SECRET.encode(), request.data, hashlib.sha256
    ).hexdigest()
    if not hmac.compare_digest(sig, expected):
        return "Firma inválida", 401

    data = request.json()
    # Procesar evento
    print(data["event"], data["data"]["vendor_name"])
    return "", 200

Autenticación

Todas las peticiones deben incluir una API key en la cabecera X-API-Key o un JWT en Authorization: Bearer <token>. Las API keys se crean desde el dashboard o via POST /keys.

API Key (recomendado para integraciones): X-API-Key: fct_xxxxxxxxxxxx. Ideal para server-to-server. Disponible desde el plan Pro.

JWT (para el dashboard): Obtén un token con POST /auth/login e inclúyelo como Authorization: Bearer <token>. Validez de 7 días.
✓ CORS — Abierto para cualquier origen

La API acepta peticiones desde cualquier dominio — puedes llamarla directamente desde tu frontend, tu ERP web, o cualquier aplicación sin configuración adicional. La seguridad la proporciona la API key, no el origen de la petición.

Si ves un error CORS o Failed to fetch en tus primeras pruebas, asegúrate de que estás incluyendo el header X-API-Key correctamente — no es un problema de origen sino de autenticación.

EJEMPLO
# Con API Key
curl -X POST https://api.facturax.app/extract \
  -H "X-API-Key: fct_tu_api_key" \
  -F "[email protected]"

# Con JWT
curl -X POST https://api.facturax.app/extract \
  -H "Authorization: Bearer eyJhbGci..." \
  -F "[email protected]"
POST /auth/login Obtener JWT de acceso

Body (JSON)

CampoTipoRequeridoDescripción
emailstringrequeridoEmail de la cuenta
passwordstringrequeridoContraseña (mínimo 8 caracteres)

Respuesta

JSON
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type":   "bearer",
  "user": {
    "id":        1,
    "email":     "[email protected]",
    "full_name": "Juan García",
    "plan":      "pro"
  }
}

Códigos de respuesta

200Login correcto. Devuelve el JWT.
401Email o contraseña incorrectos.
403Email no verificado.

Extracción de facturas

El endpoint principal. Acepta una factura en PDF, PNG, JPG o WebP y devuelve todos los campos extraídos en JSON, junto con verificaciones antifraude (Pro+) y categorización contable PGC automática.

confidence_score: Valor entre 0.0 y 1.0 que indica la fiabilidad de la extracción. Se calcula según los campos obligatorios detectados correctamente.
≥ 0.9: extracción completa, puedes procesar automáticamente.
0.7 – 0.9: extracción parcial, revisa los warnings.
< 0.7: revisión manual recomendada. La factura puede estar mal escaneada o en un formato inusual.

Cabeceras de cuota: Cada respuesta incluye X-Quota-Used, X-Quota-Limit y X-Quota-Remaining.
POST /extract Extraer datos de una factura AUTH

Request — multipart/form-data

CampoTipoRequeridoDescripción
filefilerequeridoFactura en PDF, PNG, JPG o WebP. Máximo 20MB. Soporta multipágina y escaneados.

Ejemplos

CURL
curl -X POST https://api.facturax.app/extract \
  -H "X-API-Key: fct_tu_api_key" \
  -F "file=@factura.pdf"
PYTHON
import requests

with open("factura.pdf", "rb") as f:
    res = requests.post(
        "https://api.facturax.app/extract",
        headers={"X-API-Key": "fct_tu_api_key"},
        files={"file": ("factura.pdf", f, "application/pdf")},
    )
data = res.json()

# Comprobar confianza
if data["confidence_score"] >= 0.9:
    procesar_automaticamente(data)
else:
    enviar_a_revision(data, data["warnings"])
JAVASCRIPT
const fd = new FormData();
fd.append('file', file);  // file es un objeto File del input

const res = await fetch('https://api.facturax.app/extract', {
  method: 'POST',
  headers: { 'X-API-Key': 'fct_tu_api_key' },
  body: fd
});
const data = await res.json();
console.log(`Cuota: ${res.headers.get('X-Quota-Remaining')} restantes`);

Respuesta

JSON
{
  "vendor_name":     "Iberdrola S.A.U.",
  "vendor_vat":      "A95758389",
  "vendor_address":  "C/ Hermosilla 3",
  "vendor_city":     "Madrid",
  "buyer_name":      "Mi Empresa S.L.",
  "buyer_vat":       "B12345678",
  "invoice_number":  "FAC-2024-0312",
  "invoice_date":    "2024-03-15",
  "due_date":        "2024-04-15",
  "currency":        "EUR",
  "subtotal":        154.90,
  "tax":             32.53,
  "total":           187.43,
  "iban":            "ES7621000813610123456789",
  "payment_method":  "transferencia",
  "irpf_rate":       null,
  "declared_tax_rate": 21,
  "line_items": [
    {
      "description": "Consumo eléctrico marzo",
      "quantity":    1,
      "unit_price":  154.90,
      "tax_rate":    21,
      "total":       154.90
    }
  ],
  "confidence_score": 0.97,
  "warnings":         [],
  "accounting": {
    "account_code":  "628",
    "account_label": "Suministros",
    "type":          "gasto",
    "type_label":    "Gasto del ejercicio",
    "confidence":    "alta",
    "method":        "regla",
    "reasoning":    "Iberdrola es proveedor de suministros eléctricos."
  },
  "fraud_checks": {
    "duplicate":        { "detected": false },
    "vat_verification": { "valid": true, "status": "verified" },
    "iban_check":       { "detected": false }
  },
  "_log_id": 42
}

// Cabeceras de respuesta:
// X-Quota-Used: 3
// X-Quota-Limit: 150
// X-Quota-Remaining: 147

Códigos de respuesta

200Extracción completada. Cabeceras X-Quota-* incluidas.
400Tipo de archivo no permitido o archivo corrupto.
401API key o JWT inválido.
413Archivo demasiado grande (máx. 20MB).
429Cuota agotada. Cabecera Retry-After incluida.
GET /history Historial de extracciones con filtros AUTH

Query params

CampoTipoRequeridoDescripción
limitintegeropcionalRegistros por página, 1-200 (por defecto 50)
offsetintegeropcionalRegistros a saltar para paginar (por defecto 0)
qstringopcionalBúsqueda de texto libre en filename, emisor, nº factura y external_ref
statusstringopcionalok o error para filtrar por resultado
date_fromstringopcionalFecha inicio en formato YYYY-MM-DD
date_tostringopcionalFecha fin en formato YYYY-MM-DD
subaccount_idintegeropcionalFiltrar por ID de subcuenta (solo plan Business)
La respuesta incluye el campo filters con los filtros activos y has_more para paginar. Cada item incluye external_ref y subaccount_id si se enviaron en la petición original.

Códigos de respuesta

200Lista paginada con total, has_more y objeto filters con los filtros aplicados.
GET /metrics Métricas de uso de la API AUTH
JSON
{
  "total":               87,
  "avg_confidence":      0.924,
  "total_errors":        2,
  "total_with_warnings": 11,
  "quota":               150,
  "used":                87,
  "remaining":           63,
  "plan":                "starter",
  "quota_reset_at":      "2024-04-15 08:00:00",
  "daily_usage": [
    { "day": "2024-03-15", "count": 12 }
  ]
}
GET /quota Estado de cuota actual AUTH

Forma rápida de consultar la cuota sin llamar a /metrics. Útil para mostrar en dashboards o decidir si lanzar un batch.

JSON
{
  "plan":           "starter",
  "quota":          150,
  "used":           87,
  "remaining":      63,
  "quota_reset_at": "2024-04-15 08:00:00",
  "pct_used":       58.0
}

Batch Jobs

Procesa múltiples facturas en background. El job se crea y devuelve job_id inmediatamente — no tienes que esperar. Disponible desde el plan Starter. Recibirás un email cuando termine.

Límites por plan: Starter → 20 facturas/lote · Pro → 100 · Business → 500.
El polling recomendado es cada 3-5 segundos. No hagas polling más rápido para no desperdiciar cuota de rate limit.
POST /batch/job Crear job de procesamiento en background AUTH · Starter+

Request — multipart/form-data

CampoTipoDescripción
filesfile[]Array de facturas PDF/PNG/JPG/WebP. Máx según plan.
JSON — respuesta inmediata
{
  "job_id":  "550e8400-e29b-41d4-a716-446655440000",
  "total":   25,
  "status":  "running",
  "message": "Procesando 25 facturas en background. Recibirás un email al terminar."
}
GET /batch/job/{job_id} Estado del job (polling) AUTH
JSON
{
  "job_id":      "550e8400-...",
  "status":      "done",
  "total":       25,
  "processed":  25,
  "failed":     1,
  "created_at": "2024-03-15 10:00:00",
  "finished_at":"2024-03-15 10:01:43",
  "items": [
    { "filename": "factura1.pdf", "status": "ok", "result": { ... } },
    { "filename": "factura2.pdf", "status": "error", "result": { "error": "..." } }
  ]
}
GET /batch/jobs Últimos 20 jobs del usuario AUTH

Devuelve los últimos 20 batch jobs ordenados por fecha descendente, sin los items individuales (para eso usa GET /batch/job/{job_id}).

POST /extract Extraer datos de una factura AUTH

Request — multipart/form-data

CampoTipoRequeridoDescripción
filefilerequeridoFactura en PDF, PNG o JPG. Máximo 20MB. Soporta multipágina y escaneados.

Ejemplo

CURL
curl -X POST https://api.facturax.app/extract \
  -H "X-API-Key: fct_tu_api_key" \
  -F "[email protected]"
JAVASCRIPT
const fd = new FormData();
fd.append('file', file);

const res = await fetch('https://api.facturax.app/extract', {
  method: 'POST',
  headers: { 'X-API-Key': 'fct_tu_api_key' },
  body: fd
});
const data = await res.json();

Respuesta

JSON
{
  "vendor_name":     "Iberdrola S.A.U.",
  "vendor_vat":      "A95758389",
  "invoice_number":  "FAC-2024-0312",
  "invoice_date":    "2024-03-15",
  "due_date":        "2024-04-15",
  "currency":        "EUR",
  "subtotal":        154.90,
  "tax":             32.53,
  "total":           187.43,
  "iban":            "ES7621000813610123456789",
  "line_items": [
    {
      "description": "Consumo eléctrico marzo",
      "quantity":    1,
      "unit_price":  154.90,
      "total":       187.43
    }
  ],
  "confidence_score": 0.97,
  "warnings":         [],
  "accounting": {
    "account_code":  "628",
    "account_label": "Suministros",
    "type":          "gasto",
    "confidence":    "alta"
  },
  "fraud_checks": {
    "duplicate":        { "detected": false },
    "vat_verification": { "valid": true, "status": "verified" },
    "iban_check":       { "detected": false }
  },
  "_log_id": 42
}

Códigos de respuesta

200Extracción completada. Devuelve el JSON completo.
400Tipo de archivo no permitido.
401API key o JWT inválido.
413Archivo demasiado grande (máx. 20MB).
429Cuota agotada para este plan.
GET /history Historial de extracciones AUTH

Query params

CampoTipoRequeridoDescripción
limitintegeropcionalNúmero de resultados (por defecto 50, máx. 100)

Códigos de respuesta

200Lista de extracciones con resultado JSON y metadatos.

Firmar XML Facturae

Endpoint para ERPs que ya generan su propio XML Facturae pero no tienen firma XAdES-BES integrada. Envía el XML sin firmar y devuelve el .xml listo para subir a FACe. 1 crédito por llamada.

⬡ DEMO INTERACTIVA

Prueba el firmador directamente en el navegador — sube tu .p12, selecciona un XML Facturae y descarga el resultado firmado.

Abrir firmador →
POST /sign multipart/form-data  ·  application/xml
⬡ PARA ERPs CON GENERACIÓN PROPIA DE XML

Si tu ERP ya genera el XML Facturae pero no firma con XAdES-BES, este es tu endpoint. No necesitas cambiar cómo generas las facturas — solo añades un POST y recibes el .xml firmado con el certificado FNMT del usuario.

Parámetro Tipo Requerido Descripción
fileFilerequerido*XML Facturae sin firmar (.xml). Alternativamente, envía el XML en el body con Content-Type: application/xml.
X-Certificate-IDheaderopcionalRecomendado para ERPs. ID de un certificado gestionado (obtenido con POST /certificates). Máxima prioridad — ignora los demás métodos de certificado.
X-P12-B64headeropcionalCertificado .p12 en Base64. Alternativa a X-Certificate-ID para uso puntual.
X-P12-PasswordheaderopcionalContraseña del .p12 si se envía X-P12-B64.
X-External-RefheaderopcionalID de factura en tu ERP. Se incluye en el webhook invoice.signed.
Ejemplo con certificado gestionado — recomendado para ERPs
curl -X POST https://api.facturax.app/sign \
  -H "X-API-Key: fct_tu_api_key" \
  -H "X-Certificate-ID: cert_abc123" \
  -H "X-External-Ref: F2024-001" \
  -F "file=@factura_sin_firmar.xml"
Ejemplo con certificado del perfil — para usuarios individuales
curl -X POST https://api.facturax.app/sign \
  -H "X-API-Key: fct_tu_api_key" \
  -H "X-External-Ref: F2024-001" \
  -F "file=@factura_sin_firmar.xml"
Ejemplo — Python (ERP multi-cliente con certificate_id)
import requests

# El XML lo genera tu ERP (STEL Order, Selenne, NCS, etc.)
# El certificate_id se obtiene una vez con POST /certificates
with open("factura_sin_firmar.xml", "rb") as f:
    r = requests.post(
        "https://api.facturax.app/sign",
        headers={
            "X-API-Key":        "fct_tu_api_key",
            "X-Certificate-ID": "cert_abc123",  # certificado del cliente
            "X-External-Ref":   "F2024-001",
        },
        files={"file": ("factura.xml", f, "application/xml")},
    )

data = r.json()
with open(data["filename"], "w", encoding="utf-8") as out:
    out.write(data["xml"])

print(f"Firmado: {data['filename']}")  # .xml listo para FACe
Respuesta
{
  "xml":        "<?xml version="1.0"...><ds:Signature...>",  // XML firmado con XAdES-BES
  "xml_base64": "PD94bWwgdmVyc2lvb...",              // Base64 del .xml
  "signed":     true,
  "source":     "sign",
  "log_id":     1234,
  "filename":   "factura_sin_firmar.xml"
}
Prioridad de certificado: X-Certificate-ID > X-P12-B64 > certificado del perfil. Para ERPs multi-cliente lo ideal es usar POST /certificates una vez por cliente y referenciar con X-Certificate-ID en cada llamada.

Validaciones automáticas: Rechaza XMLs que no sean Facturae (INVALID_INPUT), XMLs que ya tengan firma (INVALID_INPUT), y certificate_id inexistente o de otra cuenta (CERT_NOT_FOUND).

Facturae XML 3.2.2

Convierte el JSON extraído al formato Facturae 3.2.2, el estándar oficial español para facturación electrónica requerido por la Ley Crea y Crece. Compatible con FACe y la Administración Pública.

POST /convert-to-facturae Generar XML Facturae (devuelve JSON) AUTH

Body (JSON)

CampoTipoRequeridoDescripción
invoice_dataobjectrequeridoJSON del resultado de /extract
seller_infoobjectopcionalDatos adicionales del emisor: name, vat, address, city, zip
buyer_infoobjectopcionalDatos del receptor: name, vat, address, city, zip
signbooleanopcionalFirmar con XAdES-BES. Requiere certificate_id, p12_b64 o certificado personal guardado en la cuenta.
certificate_idstringopcionalID de un certificado gestionado (de POST /certificates). Recomendado para ERPs — evita enviar el .p12 en cada petición. Prioridad sobre p12_b64.
p12_b64stringopcionalCertificado .p12 en base64 para firma puntual. Para integraciones masivas usa certificate_id en su lugar.

Respuesta

JSON
{
  "xml":      "<?xml version=\"1.0\"?><fe:Facturae ...>",
  "version": "3.2.2",
  "signed":  false,
  "filename": "facturae_FAC-2024-0312.xml"
}
POST /convert-to-facturae/download Descargar .xml directamente AUTH

Mismo body que /convert-to-facturae pero devuelve el archivo XML directamente con Content-Disposition: attachment.

CURL
curl -X POST https://api.facturax.app/convert-to-facturae/download \
  -H "X-API-Key: fct_tu_api_key" \
  -H "Content-Type: application/json" \
  -d '{"invoice_data": {...}}' \
  -o factura.xml

Antifraude

Gestiona los IBANs de confianza de tus proveedores y confirma o descarta alertas de duplicados. Las verificaciones antifraude se ejecutan automáticamente en cada extracción.

GET /vendors Listar proveedores con IBAN registrado AUTH

Respuesta

JSON
{
  "vendors": [
    {
      "vendor_vat":   "A95758389",
      "trusted_iban": "ES7621000813610123456789",
      "last_seen":    "2024-03-15 10:32:00"
    }
  ]
}
PUT /vendors/{vendor_vat}/iban Actualizar IBAN de confianza de un proveedor AUTH

Path params

CampoTipoDescripción
vendor_vatstringCIF/NIF del proveedor (ej: A95758389)

Body (JSON)

CampoTipoDescripción
ibanstringNuevo IBAN de confianza (sin espacios)

Categorización contable

Asigna automáticamente la cuenta del Plan General Contable español usando reglas deterministas + IA con el PGC completo embebido.

POST /categorize Categorizar una factura según el PGC AUTH

Body (JSON)

CampoTipoRequeridoDescripción
vendor_namestringopcionalNombre del emisor
vendor_vatstringopcionalCIF/NIF del emisor
line_itemsarrayopcionalLíneas de la factura con description
totalnumberopcionalImporte total (influye en gasto vs inversión)

Respuesta

JSON
{
  "account_code":  "628",
  "account_label": "Suministros",
  "type":          "gasto",
  "type_label":    "Gasto del ejercicio",
  "reasoning":    "Iberdrola es un proveedor de suministros eléctricos.",
  "confidence":   "alta",
  "method":       "regla",
  "suggestions":  []
}

API Keys

Gestiona las API keys para integraciones externas. Disponible en planes Pro y Business.

POST /keys Crear nueva API key JWT

Body (JSON)

CampoTipoRequeridoDescripción
namestringrequeridoNombre descriptivo (ej: "Mi ERP")
planstringopcional"free" o "pro". Por defecto "free".
Importante: La API key solo se muestra una vez al crearla. Guárdala en un lugar seguro.

Webhooks — Notificaciones en tiempo real

En lugar de hacer polling cada X segundos preguntando "¿ya está mi factura?", configura un webhook y FacturaX avisará a tu servidor en el momento en que termine el procesamiento. Disponible en el plan Pro y superior.

SIN WEBHOOK (polling)
Tu servidor → GET /status → espera
Tu servidor → GET /status → espera
Tu servidor → GET /status → listo
CON WEBHOOK (push)
FacturaX procesa factura
FacturaX → POST tuservidor.com/hook
Tu servidor recibe el resultado ✓
Verificación de firma: Cada webhook incluye la cabecera X-Factura-Signature: sha256=HASH. Verifica que el hash coincide con HMAC-SHA256(payload, tu_secret) para confirmar que la petición es auténtica.
VERIFICACIÓN EN NODE.JS
const crypto = require('crypto');

function verifyWebhook(payload, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}
POST /webhooks Crear webhook AUTH

Body (JSON)

CampoTipoRequeridoDescripción
urlstringrequeridoURL HTTPS donde recibirás los eventos
eventsarrayopcional["*"] para todos, o uno o varios de: "invoice.extracted", "invoice.signed", "quota.warning". Por defecto ["*"].
secretstringopcionalSecret para firmar con HMAC-SHA256. Si no se provee, se genera automáticamente.

Payload del webhook

JSON — invoice.extracted
{
  "event":     "invoice.extracted",
  "timestamp": 1710500000,
  "data": {
    "log_id":         42,
    "external_ref":   "ERP-INV-2025-0312",
    "filename":       "factura.pdf",
    "vendor_name":    "Iberdrola S.A.U.",
    "vendor_vat":     "A95758389",
    "invoice_number": "FAC-2024-0312",
    "total":          187.43,
    "confidence":     0.97,
    "accounting": { "account_code": "628" },
    "fraud_checks": { "duplicate": { "detected": false } }
  }
}
JSON — invoice.signed
{
  "event":     "invoice.signed",
  "timestamp": 1710500003,
  "data": {
    "log_id":             42,
    "external_ref":       "ERP-INV-2025-0312",
    "b2_file_id":         "4_z1a2b3c4...",
    "invoice_number":     "FAC-2024-0312",
    "invoice_date":       "2024-03-12",
    "expires_at":         "2028-03-12",
    "download_endpoint":  "/invoices/42/facturae"
  }
}
JSON — quota.warning
{
  "event":     "quota.warning",
  "timestamp": 1710500010,
  "data": {
    "used":        128,
    "quota":       150,
    "remaining":   22,
    "pct_remaining": 14.7,
    "plan":        "starter",
    "message":     "Te quedan solo 22 facturas (14.7% de tu saldo)."
  }
}
invoice.signed se dispara cuando el XML XAdES-BES está en custodia. Usa download_endpoint para descargarlo sin polling.
quota.warning dispara al 15% restante de facturas para que el ERP pueda alertar antes de quedarse sin saldo.
DELETE /webhooks/{id} Eliminar webhook AUTH

Códigos de respuesta

200Webhook eliminado correctamente.
404Webhook no encontrado.

Certificados gestionados

Para ERPs con múltiples clientes. Sube el .p12 de cada empresa una sola vez y obtén un certificate_id. Úsalo en /convert-to-facturae sin reenviar el archivo en cada petición. Disponible en planes Pro y Business.

Flujo ERP multi-cliente: POST /certificates (una vez por cliente) → guarda el certificate_id → pásalo en cada POST /convert-to-facturae/download como {"certificate_id": "cert_...", "sign": true}
POST /certificates Subir certificado y obtener certificate_id AUTH

Multipart form-data. El raw_key del certificado no se puede recuperar después — almacena el certificate_id.

Form fields

CampoTipoRequeridoDescripción
filefilerequeridoArchivo .p12 o .pfx del cliente
namestringrequeridoNombre descriptivo, ej: "Acme S.L. — FNMT 2025"
passwordstringopcionalContraseña del .p12 (se cifra con Fernet antes de guardar)
PYTHON — FLUJO COMPLETO ERP
# 1. Subir el .p12 del cliente una sola vez
with open("acme_fnmt.p12", "rb") as f:
    r = requests.post(f"{BASE_URL}/certificates",
        headers=HEADERS,
        files={"file": f},
        data={"name": "Acme S.L. — FNMT 2025", "password": "mi_password"}
    )
cert = r.json()
cert_id = cert["certificate_id"]  # "cert_a1b2c3d4..." — guardar en tu BD

# 2. En cada factura, solo enviar el JSON + certificate_id
r = requests.post(f"{BASE_URL}/convert-to-facturae/download",
    headers=HEADERS,
    json={
        "invoice_data": { # ... datos de la factura },
        "sign":           True,
        "certificate_id": cert_id,   # sin reenviar el .p12
        "external_ref":   "ACME-2025-001",
    }
)
200Devuelve certificate_id, subject y expires_at del certificado.
400CERT_INVALID_FORMAT / CERT_INVALID_PASSWORD / CERT_TOO_LARGE
403PLAN_REQUIRED — solo Pro y Business
GET /certificates Listar certificados gestionados AUTH

Devuelve todos los certificados activos con su id, name, subject y expires_at. No devuelve el .p12.

DELETE /certificates/{certificate_id} Eliminar certificado gestionado AUTH

Soft delete — el certificado queda inactivo. Las facturas firmadas previamente no se ven afectadas.

Subaccounts

Multi-tenant para ERPs y agencias. Crea subcuentas independientes por empresa cliente — cuota, historial y API keys aislados. Disponible en el plan Business.

Flujo típico: crear subcuenta → crear API key con cuota propia → el ERP usa esa key con X-External-Ref → consultar historial y métricas por subcuenta desde el dashboard o la API.
GET /subaccounts/summary Resumen ejecutivo con alertas de cuota AUTH

Uso agregado, facturas 30 días y alertas automáticas al 90% de cuota.

JSON
{
  "count": 3, "total_used": 87, "total_quota": 450, "pct_used": 19.3,
  "alerts": [{ "subaccount": "Acme S.L.", "slug": "acme-sl", "pct": 94.0 }],
  "subaccounts": [{ "id": 1, "name": "Acme S.L.", "total_used": 47, "total_quota": 50 }]
}
POST /subaccounts Crear subcuenta + asignar API key AUTH
PYTHON — FLUJO COMPLETO
# 1. Crear subcuenta
sub = requests.post(f"{BASE_URL}/subaccounts", headers=HEADERS,
    json={"name": "Acme S.L.", "notes": "CIF B12345678"}).json()

# 2. Crear API key con cuota independiente
key = requests.post(f"{BASE_URL}/subaccounts/{sub['id']}/keys", headers=HEADERS,
    json={"name": "Acme — Prod", "quota": 150}).json()
acme_key = key["raw_key"]  # guardar — solo se devuelve una vez

# 3. Extraer con la key de Acme + su ID de factura
requests.post(f"{BASE_URL}/extract",
    headers={"X-API-Key": acme_key, "X-External-Ref": "ACME-2025-001"},
    files={"file": open("factura.pdf", "rb")})

# 4. Consultar historial de Acme con filtros
hist = requests.get(f"{BASE_URL}/subaccounts/{sub['id']}/history",
    headers=HEADERS,
    params={"q": "Iberdrola", "date_from": "2025-05-01", "status": "ok"}).json()
GET /subaccounts/{id}/history Historial filtrable por texto, estado y fechas AUTH
CampoTipoDescripción
qstringTexto libre: filename, emisor, nº factura, external_ref
statusstringok o error
date_from / date_tostringYYYY-MM-DD — rango de fechas
limit / offsetintegerPaginación (máx 200/página)
GET /subaccounts/{id}/metrics Métricas con top proveedores y uso diario AUTH
JSON
{ "total": 47, "avg_confidence": 0.934, "total_errors": 1,
  "keys": [{ "name": "Acme — Prod", "quota": 150, "used": 47, "remaining": 103 }],
  "daily_usage": [{ "day": "2025-05-01", "count": 8 }],
  "top_vendors": [{ "vendor": "Iberdrola S.A.U.", "count": 12 }] }