Facturar en España
Emitir una factura
POST /api/v1/invoices/issue · scope invoices:write · responde 201
El payload mínimo es corto: un comprador y una línea con su IVA.
{ "comprador": { "identificacion": "12345678Z", "razon_social": "CLIENTE EJEMPLO S.L." }, "items": [ { "descripcion": "Servicio de soporte", "cantidad": 1, "precio_unitario": 100.00, "impuestos": [{ "codigo_impuesto": "ES_IVA_21" }] } ]}Campos de primer nivel
Sección titulada «Campos de primer nivel»| Campo | Obligatorio | Por defecto | Notas |
|---|---|---|---|
comprador | ✅ | — | Sin identificación se emite una factura simplificada |
items | ✅ | — | Al menos una línea, cada una con su IVA |
referencia_externa | — | — | Envíala siempre. Tu identificador de la venta |
fecha_emision | — | Hoy en España | YYYY-MM-DD. No puede ser futura |
fecha_vencimiento | — | — | Para ventas a crédito |
retencion | — | — | Retención de IRPF en porcentaje: 1, 2, 7, 15 o 19 |
recargo_equivalencia | — | false | Repercutir recargo al comerciante minorista |
totales | — | — | Si lo envías, se comprueba |
moneda | — | EUR | España admite otras divisas del BCE |
enviar_email | — | true | Enviar el PDF al comprador |
notas | — | — | Texto libre interno |
comprador
Sección titulada «comprador»{ "identificacion": "12345678Z", "razon_social": "CLIENTE EJEMPLO S.L.", "email": "facturas@ejemplo.es", "telefono": "600123456", "direccion": "Gran Vía 28, Madrid", "pais": "FR"}| Campo | Obligatorio | Notas |
|---|---|---|
identificacion | — | NIF, CIF o NIE. Vacío emite una factura simplificada |
razon_social | — | Obligatoria si no hay identificación |
pais | — | ISO alfa-2. Vacío = España. Obligatorio si la identificación no es española |
email | — | Necesario para que le llegue el PDF |
telefono, direccion | — | Salen en la factura |
No hay tipo_identificacion: eso es del SRI ecuatoriano. En España lo que decide cómo se identifica al
comprador ante la AEAT es el país. Ver Clientes de otros países.
Facturas simplificadas
Sección titulada «Facturas simplificadas»Sin identificación, la factura se emite como simplificada (tipo F2) y usa la serie T:
{ "comprador": { "razon_social": "Cliente mostrador" }, "items": [ … ] }La normativa limita su importe; por encima de ese umbral hay que identificar al comprador.
El comprador se guarda como contacto
Sección titulada «El comprador se guarda como contacto»Si el NIF no corresponde a ningún cliente del negocio, Olympus crea el contacto automáticamente. Se desactiva por clave, desde Configuración → Integraciones.
{ "codigo_principal": "SRV-001", "descripcion": "Servicio de soporte mensual", "cantidad": 1, "unidad_medida": "Unidad", "precio_unitario": 100.00, "descuento": 0, "impuestos": [{ "codigo_impuesto": "ES_IVA_21" }]}| Campo | Obligatorio | Notas |
|---|---|---|
descripcion | ✅ | Lo que ve el cliente en la factura |
cantidad | ✅ | Mayor que cero |
precio_unitario | ✅ | Sin impuestos, dos decimales |
impuestos | ✅ | Obligatorio. Cada línea declara su IVA |
codigo_principal | — | Si coincide con el SKU de un producto, la línea se enlaza a él |
descuento | — | En importe, no en porcentaje |
unidad_medida | — | Por defecto Unidad |
Enlazar con productos del negocio
Sección titulada «Enlazar con productos del negocio»Si codigo_principal coincide con el SKU de un producto de Olympus, la línea queda enlazada y, si es
un bien, descuenta stock. Sin existencias, la emisión falla con 422 insufficient_stock.
totales
Sección titulada «totales»{ "total_sin_impuestos": 100.00, "descuento": 0, "importe_total": 121.00 }Opcional pero muy recomendable: Olympus compara con lo que calcula y rechaza la emisión si no cuadra
(422 totals_mismatch, tolerancia 0,01).
La respuesta
Sección titulada «La respuesta»201 Created:
{ "id": "6f1e8a24-91cd-4c73-9a1f-2b0f5d7e4c88", "tipo": "factura", "pais": "ES", "estado": "registrado", "secuencial": "F2026-0001", "huella": "3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9D2CF5C7BCBFB9A2D62FB", "huella_anterior": "A1B2…", "referencia_aeat": null, "fecha_registro": "2026-09-25T10:31:07+02:00", "tipo_factura": "F1", "ambiente": "produccion", "totales": { "total_sin_impuestos": 100.0, "descuento": 0.0, "iva": 21.0, "importe_total": 121.0 }, "urls": { "detalle": "…", "pdf": "…", "xml": null, "verificacion": "https://…/ValidarQR?…" }}| Campo | Qué es |
|---|---|
secuencial | Número completo de la factura (F2026-0001) |
huella | SHA-256 del registro, encadenada con la factura anterior. Identifica el comprobante |
huella_anterior | La huella del registro anterior; vacía en la primera factura |
tipo_factura | Clasificación AEAT: F1 completa, F2 simplificada, F3 sustitutiva, R1/R5 rectificativas |
referencia_aeat | El CSV que devuelve la AEAT al aceptar el registro. null hasta entonces |
urls.verificacion | La URL del QR: comprueba la factura en la sede de la AEAT |
urls.xml | Siempre null: VERI*FACTU no deja un XML descargable |
Ejemplo completo
Sección titulada «Ejemplo completo»curl -X POST https://olympus.atharix.com/api/v1/invoices/issue \ -H "X-API-Key: $OLYMPUS_API_KEY" \ -H "Idempotency-Key: venta-4821" \ -H "Content-Type: application/json" \ -d '{ "referencia_externa": "SO-2026-0912", "comprador": { "identificacion": "B87654321", "razon_social": "CLIENTE EJEMPLO S.L.", "email": "facturas@ejemplo.es", "direccion": "Gran Vía 28, Madrid" }, "items": [ { "codigo_principal": "SRV-001", "descripcion": "Servicio de consultoría", "cantidad": 10, "precio_unitario": 60.00, "impuestos": [{ "codigo_impuesto": "ES_IVA_21" }] }, { "codigo_principal": "LIB-002", "descripcion": "Manual técnico", "cantidad": 1, "precio_unitario": 30.00, "impuestos": [{ "codigo_impuesto": "ES_IVA_4" }] } ], "retencion": 15, "totales": { "total_sin_impuestos": 630.00, "importe_total": 757.20 } }'factura = client.post( "/invoices/issue", headers={"Idempotency-Key": f"venta-{venta.id}"}, json={ "referencia_externa": venta.codigo, "comprador": { "identificacion": venta.cliente.nif, "razon_social": venta.cliente.razon_social, "email": venta.cliente.email, "direccion": venta.cliente.direccion, "pais": venta.cliente.pais or "", }, "items": [ { "codigo_principal": linea.sku, "descripcion": linea.descripcion, "cantidad": float(linea.cantidad), "precio_unitario": float(linea.precio), "descuento": float(linea.descuento), "impuestos": [{"codigo_impuesto": linea.codigo_iva}], } for linea in venta.lineas ], "retencion": float(venta.retencion_irpf) if venta.retencion_irpf else None, "totales": { "total_sin_impuestos": float(venta.base), "importe_total": float(venta.total), }, },).json()
venta.numero_factura = factura["secuencial"]venta.huella = factura["huella"]venta.save()Errores frecuentes
Sección titulada «Errores frecuentes»| Error | Causa habitual |
|---|---|
business_not_ready | El negocio no tiene VERI*FACTU activo |
missing_tax_code | Una línea sin IVA, o al 0 % sin decir por qué |
field_not_supported_here | Enviaste establecimiento o secuencial, que son de Ecuador |
document_rejected_before_issuing | La factura no cumple una regla de VERI*FACTU: comprador extranjero sin país, simplificada por encima del límite, total cero |
totals_mismatch | Redondeos distintos entre tu sistema y Olympus |
external_id_conflict | La venta ya estaba facturada. Recupera la factura con details.id |
Todos, con detalle, en Errores.