Estado: preview para integradores. A API funciona e está probada, e os prezos e o reparto de responsabilidades están na páxina do Motor API. A alta aínda non ten botón: faise falando. Escríbenos a hola@factuza.com e dámosche acceso ao contorno de probas o mesmo día.
Isto non é unha lista de intencións: cada exemplo desta páxina execútase en cada pasada da batería de probas (caso CU-K04). Se algo deixase de funcionar, a proba ponse vermella antes de que o descubras ti.
Nesta páxina
1. Para quen é isto
Para software que xa xestiona un negocio —un taller, unha clínica, unha academia, un ERP vertical— e precisa que as súas facturas cumpran VeriFactu sen reescribir a parte fiscal. O teu sistema segue sendo o que manda; Factuza pon a numeración, a pegada encadeada, o PDF e a remisión.
O que non é: unha pasarela de sinatura nin un simple xerador de PDF. Cada factura que emites por aquí nace co seu rexistro de facturación, entra na cadea de pegadas e remítese á Axencia Tributaria igual que se a emitises desde a web.
Unha factura emitida non se borra. Entra na cadea de pegadas e alí queda. O que existe é anular e rectificar, que son dúas operacións distintas e as dúas deixan rastro. Proba no contorno de probas antes de emitir de verdade.
2. Autenticación: a clave de API
Toda a API acepta dúas credenciais: o testemuño dunha persoa (o que usan a web e a app) ou unha clave de API, que é a dunha máquina. Para integrar, a túa é a segunda.
Unha chave créase desde a área de clientes, en Chaves de API, co teu usuario de administrador, e viaxa nunha cabeceira:
X-Api-Key: fzk_x8Kq2vN...Ao crreala escolles que pode facer, e a diferenza importa:
- Só emitir (o normal). Emite, anula e consulta dos obrigados que xa estean dados de alta.
- Emitir e dar de alta obrigados. Ademais pode incorporar clientes novos con
POST /emisores, que é o que precisas se o teu programa os dá de alta pola súa conta.
Se a túa integración non incorpora clientes, usa a primeira: unha chave que pode menos fai menos dano o día que se filtre. E non se pode ampliar sobre a marcha — créase outra a propósito.
Tres regras que convén saber antes
- A clave vese unha vez. Ao creala amosámoscha enteira e non volve aparecer: na nosa base só queda a súa pegada SHA-256. Se a perdes, revócase e créase outra — non hai «recuperar», e é a propósito.
- Unha máquina non xestiona máquinas. Cunha clave non se poden crear nin revogar claves. Se unha se filtrase, quen a teña non pode fabricarse sucesoras nin borrar o rastro.
- Revogar é inmediato. A seguinte petición que use esa clave recibe un
401. Non hai caché nin xanela de graza.
A clave herda os permisos da túa conta: os seus emisores, o seu plan e a súa licenza. Non pode emitir a nome dun NIF que non sexa o teu, e se a túa licenza caduca deixa de emitir igual que a web — pero segue podendo consultar e descargar o xa emitido, porque os teus libros son teus.
3. A túa primeira factura, paso a paso
Cinco chamadas. Todas coa mesma cabeceira e ningunha con testemuño de usuario. O exemplo usa o contorno de probas, onde nada conta nin se remite á AEAT.
01Quen son?
Antes de nada, con que emisores pode traballar a túa clave. De aquí sae o NIF e o nome que van na factura: non os tecleés, léeos.
curl https://func-factuza-test-obt1.azurewebsites.net/api/emisores \ -H "X-Api-Key: $FACTUZA_CLAVE" { "emisores": [ { "emisorId": 2, "nif": "B87654323", "nombreRazon": "Pruebas Automaticas SL" } ], "plan": "PYME", "tope": 1, "usados": 1, "disponibles": 0 }
02A túa serie
O número de factura ponno o servidor, dentro da mesma transacción que a emite. Ti mandas o serieId, non o número: teclealo desde fóra deixa a correlatividade en mans de que ninguén se equivoque, e un oco na numeración hai que xustificalo ante Facenda.
curl https://func-factuza-test-obt1.azurewebsites.net/api/series \ -H "X-Api-Key: $FACTUZA_CLAVE" { "series": [ { "serieId": 1, "codigo": "PRU", "activa": true, "siguienteNumero": "PRU-2026/0457" } ], "total": 1 }
03Emitir
O corpo mínimo dunha factura normal (F1) cunha soa base e un só tipo. Para varias liñas ou tratamentos especiais de IVE, usa POST /facturas/detallada.
curl -X POST https://func-factuza-test-obt1.azurewebsites.net/api/facturas \ -H "X-Api-Key: $FACTUZA_CLAVE" \ -H "Content-Type: application/json" \ -d '{ "nifEmisor": "B87654323", "nombreEmisor": "Pruebas Automaticas SL", "numSerieFactura": "", // lo pone la serie "serieId": 1, "fechaExpedicion": "2026-08-23", "tipoFactura": "F1", "descripcion": "Primera factura por API", "nifDestinatario": "B12345674", "nombreDestinatario": "Cliente de pruebas SL", "baseImponible": 100, "tipoImpositivo": 21 }'
Responde 201 —creouse un recurso, non é un 200— co número que puxo a serie e a pegada:
{ "facturaId": 457,
"numSerieFactura": "PRU-2026/0457",
"huella": "9F2A…64 caracteres en hexadecimal" }Sen pegada non é VeriFactu. Son 64 caracteres: o SHA-256 do rexistro, encadeado co da factura anterior.
04O PDF
Co seu QR de comprobación, listo para mandarllo ao cliente.
curl https://func-factuza-test-obt1.azurewebsites.net/api/facturas/457/pdf \ -H "X-Api-Key: $FACTUZA_CLAVE" -o factura.pdf
05Como vas de consumo
Esta ruta si a pode chamar unha máquina, ao revés que as de xestión de claves: negarche saber por onde vas e logo cobrarche o exceso sería tenderche unha trampa.
curl https://func-factuza-test-obt1.azurewebsites.net/api/claves-api/consumo \ -H "X-Api-Key: $FACTUZA_CLAVE" { "desde": "2026-08-01…", "hasta": "2026-09-01…", "porEmisor": [ { "nif": "B87654323", "registros": 12, "incluidos": 3000, "restantes": 2988, "exceso": 0, "eurosDeExceso": 0 } ], "totalRegistros": 12, "peticionesPorMinuto": 120 }
4. Os catro fluxos que importan
Emitir
POST /facturas para o caso simple, POST /facturas/detallada cando hai varias liñas, descontos, ou tratamentos de IVE distintos do normal (exenta, inversión do suxeito pasivo, non suxeita por localización, suplido). Sen destinatario sae unha simplificada (F2), que non dá dereito a deducir o IVE a quen a recibe.
Anular e rectificar — non son o mesmo
POST /facturas/{id}/anulardeixa a factura anulada, non borrada: xera o seu propio rexistro de anulación, que tamén se encadea e se remite.POST /facturas/{id}/rectificarcrea unha factura nova que apunta á orixinal, e a orixinal segue existindo. É o que hai que facer cando o importe ou os datos estaban mal.
Unha factura xa cobrada non se anula. É unha regra do motor, non da interface.
Gastos
POST /gastos dá de alta un gasto deducible, que queda pendente ata que alguén o dá por bo con POST /gastos/{id}/validar. Esa parada é deliberada: un gasto mal lido que entra só na contabilidade non se nota ata que chega o trimestre.
Se tes a foto ou o PDF e non os datos, POST /ocr/analizar devolve un borrador co que leu e unha lista de en que non se fía de si mesmo. Nunca crea o gasto: propono.
Exportación legal
GET /exportar/registros?desde=&hasta= devolve os rexistros de facturación no formato estandarizado do artigo 10 do RD 1007/2023: un XML por rexistro, a cadea de pegadas en CSV e un manifesto para poder recalculalas e comprobar que ninguén tocou nada. É o que hai que poder entregar nunha inspección.
GET /exportar/gestoria é outra cousa distinta: o paquete cómodo para o asesor, con PDF e CSV.
Enterarte do que di a AEAT (webhook)
Cando emites, o 201 chega en milisegundos, pero o que che está a dicir é «recollido e encolado». A AEAT contesta despois, e esa resposta —aceptado, aceptado con erros, ou rexeitado cun código— é a que decide se a factura vale. Podes consultala con GET /facturas/{id}, pero sondar unha por unha é caro e na práctica non o fai ninguén: os rexeitamentos descóbrense semanas máis tarde, cando xa emitiches cen máis co mesmo erro.
Así que dinos onde avisarte. Desde Área → Chaves de API → Webhook, ou pola propia API:
PUT /webhook
{ "url": "https://api.tu-erp.es/factuza/avisos" }
→ 200 { "secreto": "whsec_…", "cabecera": "X-Factuza-Firma" }
O segredo amósase unha soa vez. Gárdao: é co que comprobas que o aviso vén de nós. Se o perdes, volves gardar a URL e xérase outro (cambiar a URL sempre renova o segredo, para que o que asinaba cara ao teu servidor anterior deixe de valer).
Cada aviso chega como un POST con este corpo:
{ "evento": "registro.remitido",
"registroId": 48211,
"numSerieFactura": "FA2026-0117",
"nifEmisor": "B12345674",
"resultado": "RECHAZADO",
"codigoError": "1103",
"descripcionError": "El NIF del destinatario no está identificado",
"csv": null,
"ocurridoUtc": "2026-08-25T09:14:03Z" }
resultado é ACEPTADO, ACEPTADO_ERRORES ou RECHAZADO. Só avisamos de estados definitivos: se a AEAT non está dispoñible e hai que reintentar, iso é cousa nosa e non cho contamos.
Como se comproba a sinatura
A cabeceira X-Factuza-Firma chega así: t=1756112043,v1=<hex>. O v1 é o HMAC-SHA256 da cadea «<t>.<corpo tal cal chegou>» co teu segredo.
firmado = t + "." + cuerpoCrudo
esperado = hmac_sha256(secreto, firmado).hex()
valido = comparacionEnTiempoConstante(esperado, v1) and (ahora - t) < 300
Compara o tempo tamén. A marca t vai dentro do asinado precisamente para iso: sen comprobala, calquera que intercepte un aviso pode reenviarcho meses despois e a sinatura seguirá cadrando. Rexeita o que chegue con máis de cinco minutos.
E usa o corpo cru, antes de analizar o JSON: se o reserializas, a orde das chaves ou os espazos poden cambiar e a sinatura deixará de coincidir.
Reintentos e duplicados
Se o teu servidor non contesta un 2xx en dez segundos, reintentamos a 1, 5, 15, 60, 180, 360 e 720 minutos, e rendémonos ao oitavo intento —unhas vinte horas—. Na área verás cada entrega e por que fallou.
Cada reintento leva a mesma cabeceira X-Factuza-Entrega, así que se recibiches o aviso pero caíches antes de contestar, recoñecerálo por ese número e non o procesarás dúas veces. Contesta 200 primeiro e fai o traballo despois: o que tardes dentro da petición conta contra eses dez segundos.
Dúas cousas que rexeitamos e convén saber de antemán: a URL ten que ser https:// e apuntar a un servidor público —un aviso leva o NIF do obrigado dentro, e comprobamos o enderezo xusto antes de chamar—, e non seguimos redireccións: se contestas un 302 contámolo como fallo. Apunta o webhook á URL final.
5. Cotas e límites
| Límite | Canto | Que pasa ao pasarse |
|---|---|---|
| Rexistros por emisor e mes | 3.000 incluídos | Nada se bloquea. O exceso factúrase a 2 € por cada 1.000. |
| Peticións por minuto | 120 | 429 con cabeceira Retry-After. |
A asimetría é deliberada e paga a pena entendela: un tope mensual que cortase a emisión deixaría a un obrigado tributario sen poder cumprir a lei por un asunto comercial noso. Emitir unha factura non é un capricho, ten data. Así que o tope mensual avisa e cóbrase.
O límite por minuto si corta, e por un motivo distinto: un bucle roto nunha integración non é a obriga legal de ninguén — é unha avaría que, sen freo, leva por diante o servizo dos demais. O 429 ademais avísate de que tes un fallo.
6. Erros
| Código | Que significa |
|---|---|
400 | O corpo non vale. A mensaxe di que campo e por que, en castelán. |
401 | Sen credencial válida: falta a cabeceira, a clave non existe ou está revogada. |
403 | A túa conta non pode facer iso: rol insuficiente, licenza non vixente, ou un emisor que non é teu. |
404 | Non existe — ou non é teu. Pedir o recurso doutra conta responde igual que pedir un que non existe, a propósito. |
409 | Conflito: xa existe, ou o estado non permite a operación (anular unha factura cobrada). |
429 | Demasiadas peticións por minuto. Agarda o que diga Retry-After. |
Os erros traen un corpo JSON con error e, cando axuda, un detalle. Están escritos para que se entendan sen coñecer as nosas tripas: «No puedes emitir facturas a nombre de X. Tu cuenta emite como Y» di que pasa e como se arranxa.
7. Catálogo de rutas
O máis usado. Todas colgan de https://func-factuza-prod-obt1.azurewebsites.net/api en produción e de …-test-… en probas.
| Verbo | Ruta | Que fai |
|---|---|---|
| get | /emisores | Con que emisores traballa a túa clave |
| get | /series | As túas series e o número seguinte |
| post | /facturas | Emitir (base e tipo únicos) |
| post | /facturas/detallada | Emitir con liñas e tratamentos de IVE |
| get | /facturas | Listaxe, con filtros |
| get | /facturas/{id}/pdf | O PDF co seu QR |
| post | /facturas/{id}/anular | Anular (non borra) |
| post | /facturas/{id}/rectificar | Rectificar (crea outra) |
| post | /facturas/{id}/email | Mandala por correo |
| get | /gastos | Gastos do período |
| post | /gastos | Alta de gasto (queda pendente) |
| post | /gastos/{id}/validar | Dalo por bo |
| post | /ocr/analizar | Ler unha foto ou un PDF (propón, non crea) |
| get | /maestras/destinatarios | Os teus clientes |
| get | /resumen | Facturado, trimestre e pendente de cobro |
| get | /modelos/{modelo} | Borrador do 303, 130, 390… |
| get | /exportar/registros | Exportación legal do art. 10 |
| get | /claves-api/consumo | O teu consumo do mes |
| get | /webhook | Onde te avisamos e como foron as últimas entregas |
| put | /webhook | Poñer ou cambiar a URL (devolve o segredo unha vez) |
| delete | /webhook | Deixar de avisar |
Hai máis de sesenta rutas en total —orzamentos, recorrentes, cobros, series, usuarios—. Se botas en falta algunha, escríbenos e dicímosche se existe.
8. Descargar a especificación
O catálogo completo —62 rutas, 82 operacións— en OpenAPI 3.0, para cargalo en Postman, Insomnia ou o xerador de clientes que uses:
Descargar factuza-api.yaml versión 1.0.0 · 25-ago-2026
Xérase do código, non se escribe a man. As rutas saen do propio motor e as descricións do comentario que cada unha ten ao lado, e hai unha proba que pon a suite en vermello se publicamos unha API con rutas que non están no ficheiro. É a garantía de que o que descargas é o que hai servido.
Dicímolo porque aquí había antes un ficheiro escrito a man que describía 19 rutas cando o motor xa servía 62, con nomes que non existían. Retirouse.
Dúas cousas que a especificación aínda non leva, ditas antes de que as botes en falta: os esquemas dos corpos de petición e resposta —que están nesta páxina, con exemplos que se executan en cada pasada da suite— e a colección de Postman.
9. Compromiso de versión
- Non quitamos nin renomeamos campos dunha resposta sen avisar con 90 días. Engadir campos novos si pode pasar en calquera momento: o teu cliente debe ignorar os que non coñeza.
- Non cambiamos o significado dun campo existente. Se algo ten que significar outra cousa, será un campo novo.
- Os códigos de estado son parte do contrato. Se hoxe unha operación responde
201, seguirá respondendo201. - Os cambios que rompen anúncianse por correo ás contas con clave viva, non só nunha páxina que hai que ir mirar.