Primeros pasos
De cero a tu primera factura registrada, en cuatro llamadas.
Fiscaza genera el registro de facturación, lo encadena con el anterior, calcula la huella y lo remite a la AEAT por ti. Tu software sigue emitiendo facturas como hasta ahora; lo único que cambia es que después de emitir una, se la cuentas a Fiscaza.
Todo lo que sigue funciona en el entorno de pruebas, que no envía nada a la AEAT y no tiene validez fiscal. Consigue tu clave en /registro.
Conseguir una clave de pruebas →
1. Comprueba tu clave
La clave va en la cabecera Authorization. Si esto responde 200, ya estás dentro. Las claves de pruebas empiezan por fz_test_ y las de producción por fz_live_; la clave determina el entorno, no hay que indicarlo en cada llamada.
curl -X GET https://fiscaza.com/api/v1/health \ -H "Authorization: Bearer fz_test_…"
Respuesta
{
"status": "ok",
"environment": "sandbox",
"checks": [ … ]
}Detalle. Este endpoint es el mismo diagnóstico que usamos internamente, limitado a tus obligados. Devuelve 200 aunque encuentre problemas: un certificado tuyo caducado no es una caída nuestra.
2. Da de alta al obligado tributario
Un obligado es el negocio que emite las facturas: tu cliente. Se da de alta una sola vez. El NIF debe ser válido; el sistema lo comprueba antes de aceptarlo.
curl -X POST https://fiscaza.com/api/v1/taxpayers \
-H "Authorization: Bearer fz_test_…"
\
-H "Content-Type: application/json" \
-d '{
"nif": "B12345674",
"razonSocial": "Bar Ejemplo SL"
}'Respuesta
{
"nif": "B12345674",
"razon_social": "Bar Ejemplo SL",
"environment": "sandbox",
"status": "active",
"credential_mode": "own_cert",
"numero_instalacion": "20260823T120000-000001",
"can_submit": false,
"blocked": "sin certificado"
}Detalle. can_submit es false hasta que el obligado tenga certificado. Puedes seguir emitiendo registros: se acumulan y se envían en cuanto haya credenciales. El numero_instalacion lo genera Fiscaza y es inmutable.
3. Registra una factura
Esta es la llamada que harás miles de veces. Devuelve la huella, el QR y la leyenda inmediatamente: no espera a la AEAT, porque tu cliente está esperando para imprimir el ticket.
curl -X POST https://fiscaza.com/api/v1/invoices \
-H "Authorization: Bearer fz_test_…"
\
-H "Content-Type: application/json" \
-d '{
"nif": "B12345674",
"serie": "A-",
"tipoFactura": "F2",
"fechaExpedicion": "23-08-2026",
"descripcionOperacion": "Consumición",
"cuotaTotal": "2.10",
"importeTotal": "12.10",
"desglose": [
{
"impuesto": "01",
"claveRegimen": "01",
"calificacionOperacion": "S1",
"tipoImpositivo": "21.00",
"baseImponibleOimporteNoSujeto": "10.00",
"cuotaRepercutida": "2.10"
}
]
}'Respuesta
{
"id": "…",
"num_serie": "A-1",
"huella": "02BB1BAC4EFAE737…",
"qr": "https://prewww2.aeat.es/wlpl/TIKE-CONT/ValidarQR?…",
"leyenda": "VERI*FACTU",
"submission": { "status": "queued" }
}Detalle. Manda la cabecera Idempotency-Key con un identificador tuyo de la operación. Si tu proceso se cae entre la respuesta y tu commit, reintentar con la misma clave devuelve la misma factura en lugar de crear una segunda. La misma clave con un cuerpo distinto da 422.
4. Consulta el veredicto de la AEAT
El envío es asíncrono. Consulta la factura cuando quieras, o —mejor— registra un webhook y deja que te avisemos.
curl -X GET https://fiscaza.com/api/v1/invoices \ -H "Authorization: Bearer fz_test_…"
Respuesta
{
"data": [
{
"id": "…",
"num_serie": "A-1",
"submission": {
"status": "accepted",
"csv": "A-CSV-FROM-AEAT",
"aeat": null
}
}
],
"next_cursor": null
}Detalle. Estados posibles: queued, sent, accepted, accepted_with_errors, rejected. accepted_with_errors significa que la AEAT lo ha guardado igualmente: NO lo reenvíes, corrígelo con una subsanación.
Y después
- Sube el certificado del obligado, o mándale un enlace para que lo suba él: POST /v1/taxpayers/{nif}/certificate-link devuelve una URL de un solo uso que puedes enviarle por correo.
- Registra un webhook con POST /v1/webhooks y te avisamos cuando algo va mal, firmado con HMAC-SHA256, en tu idioma.
- Anula una factura con POST /v1/invoices/{id}/cancel. La anulación es otro registro encadenado, no un borrado: el libro es inalterable por ley.
Todos los endpoints
| POST | /v1/signup | Crea una cuenta de pruebas y devuelve la primera clave. Sin autenticar. |
| GET | /v1/health | Diagnóstico de tus obligados, en tu idioma. Siempre 200. |
| POST | /v1/taxpayers | Da de alta un obligado tributario. |
| GET | /v1/taxpayers | Lista tus obligados. |
| GET | /v1/taxpayers/{nif} | Consulta un obligado, incluido si puede enviar y por qué no. |
| DELETE | /v1/taxpayers/{nif} | Cierra un obligado. No borra: el libro es inalterable. |
| POST | /v1/taxpayers/{nif}/certificate | Sube el certificado del obligado (base64 del .p12 y contraseña). |
| POST | /v1/taxpayers/{nif}/certificate-link | Genera un enlace de un solo uso para que lo suba el titular. |
| POST | /v1/invoices | Registra una factura. Devuelve huella, QR y leyenda al instante. |
| GET | /v1/invoices | Lista facturas con paginación por cursor y filtros. |
| GET | /v1/invoices/{id} | Consulta una factura y el veredicto de la AEAT. |
| POST | /v1/invoices/{id}/cancel | Anula una factura mediante un registro de anulación encadenado. |
| POST | /v1/invoices/{id}/subsanar | Corrige una factura que la AEAT ya tiene, con la misma identidad. |
| POST | /v1/webhooks | Registra un destino de webhook. Devuelve el secreto una sola vez. |
| GET | /v1/webhooks | Lista tus webhooks y su salud de entrega. |
| POST | /v1/webhooks/{id}/rotate | Rota el secreto de firma. El anterior deja de valer al instante. |
| POST | /v1/webhooks/{id}/resume | Reactiva un webhook pausado por fallos repetidos. |
| POST | /v1/api-keys | Crea otra clave en tu entorno actual. Se muestra una sola vez. |
| GET | /v1/api-keys | Lista tus claves: prefijo, etiqueta, último uso, revocación. Nunca la clave. |
| DELETE | /v1/api-keys/{prefix} | Revoca una clave al instante. La única activa no se puede revocar a sí misma: crea otra antes. |
Errores
Todos los errores tienen la misma forma: un código estable que puedes programar, un mensaje en tu idioma que puedes enseñar, y un request_id.
{
"error": {
"code": "invalid_request",
"message": "Faltan campos obligatorios.",
"fields": [ { "field": "nif", "problem": "required", "detail": "…" } ],
"locale": "es",
"request_id": "76020a7a-…"
}
}| 401 | unauthorized | Clave ausente, desconocida, revocada o suspendida. No distinguimos entre ellas. |
| 400 | invalid_request | Validación fallida. Trae fields[] con el campo y el motivo, en tu idioma. |
| 404 | not_found | No existe, o no es tuyo. Devolvemos 404 en ambos casos a propósito. |
| 409 | conflict | La operación choca con el estado actual (por ejemplo, anular dos veces). |
| 422 | idempotency_conflict | Misma Idempotency-Key con un cuerpo distinto. |
| 429 | rate_limited | Has superado el límite por minuto de tu clave. |
| 500 | internal | Fallo nuestro. Trae un request_id; mándanoslo. |