Per a desenvolupadors

L'API de Factuza

El mateix motor que fan servir el web i l'app: empremta encadenada SHA-256, numeració per sèrie, PDF amb QR de comprovació i remissió a l'AEAT.

Estat: preview per a integradors. L'API funciona i està provada, i els preus i el repartiment de responsabilitats són a la pàgina del Motor API. L'alta encara no té botó: es fa parlant. Escriu-nos a hola@factuza.com i et donem accés a l'entorn de proves el mateix dia.

Això no és una llista d'intencions: cada exemple d'aquesta pàgina s'executa en cada passada de la bateria de proves (cas CU-K04). Si alguna cosa deixés de funcionar, la prova es posa vermella abans que ho descobreixis tu.

1. Per a qui és això

Per a programari que ja gestiona un negoci —un taller, una clínica, una acadèmia, un ERP vertical— i necessita que les seves factures compleixin VeriFactu sense reescriure la part fiscal. El teu sistema continua sent el que mana; Factuza hi posa la numeració, l'empremta encadenada, el PDF i la remissió.

El que no és: una passarel·la de signatura ni un simple generador de PDF. Cada factura que emets per aquí neix amb el seu registre de facturació, entra a la cadena d'empremtes i es remet a l'Agència Tributària igual que si l'haguessis emès des del web.

Una factura emesa no s'esborra. Entra a la cadena d'empremtes i allà es queda. El que existeix és anul·lar i rectificar, que són dues operacions diferents i totes dues deixen rastre. Prova a l'entorn de proves abans d'emetre de debò.

2. Autenticació: la clau d'API

Tota l'API accepta dues credencials: el testimoni d'una persona (el que fan servir el web i l'app) o una clau d'API, que és la d'una màquina. Per integrar, la teva és la segona.

Una clau es crea des de l'àrea de clients, a Claus d'API, amb el teu usuari d'administrador, i viatja en una capçalera:

X-Api-Key: fzk_x8Kq2vN...

En crear-la tries què pot fer, i la diferència importa:

Si la teva integració no incorpora clients, fes servir la primera: una clau que pot menys fa menys mal el dia que es filtri. I no es pot ampliar sobre la marxa — se'n crea una altra expressament.

Tres regles que convé saber abans

La clau hereta els permisos del teu compte: els seus emissors, el seu pla i la seva llicència. No pot emetre a nom d'un NIF que no sigui el teu, i si la teva llicència caduca deixa d'emetre igual que el web — però continua podent consultar i descarregar el ja emès, perquè els teus llibres són teus.

3. La teva primera factura, pas a pas

Cinc crides. Totes amb la mateixa capçalera i cap amb testimoni d'usuari. L'exemple fa servir l'entorn de proves, on res no compta ni es remet a l'AEAT.

01Qui sóc?

Abans de res, amb quins emissors pot treballar la teva clau. D'aquí surten el NIF i el nom que van a la factura: no els teclegis, llegeix-los.

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 }

02La teva sèrie

El número de factura el posa el servidor, dins de la mateixa transacció que l'emet. Tu envies el serieId, no el número: teclejar-lo des de fora deixa la correlativitat en mans que ningú no s'equivoqui, i un forat a la numeració s'ha de justificar davant d'Hisenda.

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 }

03Emetre

El cos mínim d'una factura normal (F1) amb una sola base i un sol tipus. Per a diverses línies o tractaments especials d'IVA, fes servir 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
  }'

Respon 201 —s'ha creat un recurs, no és un 200— amb el número que ha posat la sèrie i l'empremta:

{ "facturaId": 457,
  "numSerieFactura": "PRU-2026/0457",
  "huella": "9F2A…64 caracteres en hexadecimal" }

Sense empremta no és VeriFactu. Són 64 caràcters: el SHA-256 del registre, encadenat amb el de la factura anterior.

04El PDF

Amb el seu QR de comprovació, a punt per enviar-lo al client.

curl https://func-factuza-test-obt1.azurewebsites.net/api/facturas/457/pdf \
  -H "X-Api-Key: $FACTUZA_CLAVE" -o factura.pdf

05Com vas de consum

Aquesta ruta que la pot cridar una màquina, al revés que les de gestió de claus: negar-te saber per on vas i després cobrar-te l'excés seria parar-te un parany.

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. Els quatre fluxos que importen

Emetre

POST /facturas per al cas simple, POST /facturas/detallada quan hi ha diverses línies, descomptes, o tractaments d'IVA diferents del normal (exempta, inversió del subjecte passiu, no subjecta per localització, suplert). Sense destinatari surt una simplificada (F2), que no dona dret a deduir l'IVA a qui la rep.

Anul·lar i rectificar — no són el mateix

Una factura ja cobrada no s'anul·la. És una regla del motor, no de la interfície.

Despeses

POST /gastos dona d'alta una despesa deduïble, que queda pendent fins que algú la dona per bona amb POST /gastos/{id}/validar. Aquella aturada és deliberada: una despesa mal llegida que entra sola a la comptabilitat no es nota fins que arriba el trimestre.

Si tens la foto o el PDF i no les dades, POST /ocr/analizar retorna un esborrany amb el que ha llegit i una llista de què no es fia d'ell mateix. Mai no crea la despesa: la proposa.

Exportació legal

GET /exportar/registros?desde=&hasta= retorna els registres de facturació en el format estandarditzat de l'article 10 del RD 1007/2023: un XML per registre, la cadena d'empremtes en CSV i un manifest per poder recalcular-les i comprovar que ningú no ha tocat res. És el que cal poder lliurar en una inspecció.

GET /exportar/gestoria és una altra cosa diferent: el paquet còmode per a l'assessor, amb PDF i CSV.

Assabentar-te del que diu l'AEAT (webhook)

Quan emets, el 201 arriba en mil·lisegons, però el que et diu és «recollit i encuat». L'AEAT contesta després, i aquesta resposta —acceptat, acceptat amb errors, o rebutjat amb un codi— és la que decideix si la factura val. Pots consultar-la amb GET /facturas/{id}, però sondejar-les una a una és car i a la pràctica no ho fa ningú: els rebuigs es descobreixen setmanes més tard, quan ja n'has emès cent més amb el mateix error.

Així que digues-nos on avisar-te. Des d'Àrea → Claus d'API → Webhook, o per la mateixa API:

PUT /webhook
{ "url": "https://api.tu-erp.es/factuza/avisos" }

→ 200 { "secreto": "whsec_…", "cabecera": "X-Factuza-Firma" }

El secret es mostra una sola vegada. Desa'l: és amb el que comproves que l'avís ve de nosaltres. Si el perds, tornes a desar la URL i se'n genera un altre (canviar la URL sempre renova el secret, perquè el que signava cap al teu servidor anterior deixi de valer).

Cada avís arriba com un POST amb aquest cos:

{ "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 és ACEPTADO, ACEPTADO_ERRORES o RECHAZADO. Només avisem d'estats definitius: si l'AEAT no està disponible i cal reintentar, això és cosa nostra i no t'ho expliquem.

Com es comprova la signatura

La capçalera X-Factuza-Firma arriba així: t=1756112043,v1=<hex>. El v1 és l'HMAC-SHA256 de la cadena «<t>.<cos tal com va arribar>» amb el teu secret.

firmado = t + "." + cuerpoCrudo
esperado = hmac_sha256(secreto, firmado).hex()
valido   = comparacionEnTiempoConstante(esperado, v1) and (ahora - t) < 300

Compara el temps també. La marca t va dins del que se signa precisament per això: sense comprovar-la, qualsevol que intercepti un avís te'l pot reenviar mesos després i la signatura continuarà quadrant. Rebutja el que arribi amb més de cinc minuts.

I fes servir el cos cru, abans d'analitzar el JSON: si el reserialitzes, l'ordre de les claus o els espais poden canviar i la signatura deixarà de coincidir.

Reintents i duplicats

Si el teu servidor no contesta un 2xx en deu segons, reintentem a 1, 5, 15, 60, 180, 360 i 720 minuts, i ens rendim al vuitè intent —unes vint hores—. A l'àrea veuràs cada lliurament i per què va fallar.

Cada reintent porta la mateixa capçalera X-Factuza-Entrega, així que si vas rebre l'avís però vas caure abans de contestar, el reconeixeràs per aquest número i no el processaràs dues vegades. Contesta 200 primer i fes la feina després: el que triguis dins de la petició compta contra aquests deu segons.

Dues coses que rebutgem i convé saber per endavant: la URL ha de ser https:// i apuntar a un servidor públic —un avís porta el NIF de l'obligat a dins, i comprovem l'adreça just abans de trucar—, i no seguim redireccions: si contestes un 302 ho comptem com a fallada. Apunta el webhook a la URL final.

5. Quotes i límits

LímitQuantQuè passa en passar-se'n
Registres per emissor i mes 3.000 inclosos Res no es bloqueja. L'excés es factura a 2 € per cada 1.000.
Peticions per minut 120 429 amb capçalera Retry-After.

L'asimetria és deliberada i val la pena entendre-la: un topall mensual que tallés l'emissió deixaria un obligat tributari sense poder complir la llei per un assumpte comercial nostre. Emetre una factura no és un caprici, té data. Així que el topall mensual avisa i es cobra.

El límit per minut sí que talla, i per un motiu diferent: un bucle trencat en una integració no és l'obligació legal de ningú — és una avaria que, sense fre, s'enduu per davant el servei dels altres. El 429 a més t'avisa que tens una fallada.

6. Errors

CodiQuè significa
400El cos no val. El missatge diu quin camp i per què, en castellà.
401Sense credencial vàlida: falta la capçalera, la clau no existeix o està revocada.
403El teu compte no pot fer això: rol insuficient, llicència no vigent, o un emissor que no és teu.
404No existeix — o no és teu. Demanar el recurs d'un altre compte respon igual que demanar-ne un que no existeix, a propòsit.
409Conflicte: ja existeix, o l'estat no permet l'operació (anul·lar una factura cobrada).
429Massa peticions per minut. Espera el que digui Retry-After.

Els errors porten un cos JSON amb error i, quan ajuda, un detalle. Estan escrits perquè s'entenguin sense conèixer les nostres entranyes: «No puedes emitir facturas a nombre de X. Tu cuenta emite como Y» diu què passa i com s'arregla.

El més utilitzat. Totes pengen de https://func-factuza-prod-obt1.azurewebsites.net/api en producció i de …-test-… en proves.

VerbRutaQuè fa
get/emisoresAmb quins emissors treballa la teva clau
get/seriesLes teves sèries i el número següent
post/facturasEmetre (base i tipus únics)
post/facturas/detalladaEmetre amb línies i tractaments d'IVA
get/facturasLlistat, amb filtres
get/facturas/{id}/pdfEl PDF amb el seu QR
post/facturas/{id}/anularAnul·lar (no esborra)
post/facturas/{id}/rectificarRectificar (en crea una altra)
post/facturas/{id}/emailEnviar-la per correu
get/gastosDespeses del període
post/gastosAlta de despesa (queda pendent)
post/gastos/{id}/validarDonar-la per bona
post/ocr/analizarLlegir una foto o un PDF (proposa, no crea)
get/maestras/destinatariosEls teus clients
get/resumenFacturat, trimestre i pendent de cobrament
get/modelos/{modelo}Esborrany del 303, 130, 390…
get/exportar/registrosExportació legal de l'art. 10
get/claves-api/consumoEl teu consum del mes
get/webhookOn t'avisem i com van anar els últims lliuraments
put/webhookPosar o canviar la URL (torna el secret un cop)
delete/webhookDeixar d'avisar

Hi ha més de seixanta rutes en total —pressupostos, recurrents, cobraments, sèries, usuaris—. Si te'n falta alguna, escriu-nos i et diem si existeix.

8. Descarregar l'especificació

El catàleg complet —62 rutes, 82 operacions— en OpenAPI 3.0, per carregar-lo a Postman, Insomnia o el generador de clients que facis servir:

Descarregar factuza-api.yaml versió 1.0.0 · 25-ago-2026

Es genera del codi, no s'escriu a mà. Les rutes surten del mateix motor i les descripcions del comentari que cadascuna té al costat, i hi ha una prova que posa la suite en vermell si publiquem una API amb rutes que no són al fitxer. És la garantia que el que et descarregues és el que hi ha servit.

Ho diem perquè aquí hi havia abans un fitxer escrit a mà que descrivia 19 rutes quan el motor ja en servia 62, amb noms que no existien. Es va retirar.

Dues coses que l'especificació encara no porta, dites abans que les trobis a faltar: els esquemes dels cossos de petició i resposta —que són en aquesta pàgina, amb exemples que s'executen a cada passada de la suite— i la col·lecció de Postman.

9. Compromís de versió