Errores
Todos los endpoints de /api/v1/* devuelven el mismo sobre de error. El campo error.code es estable: puedes ramificar tu lógica sobre él sin miedo a que cambie dentro de la v1.
Los endpoints OAuth2 /oauth/token y /oauth/revoke usan el formato de error del protocolo (por ejemplo, {"error":"invalid_grant"} si el código o refresh token no es válido), no este sobre de la API pública. Consulta Autenticación y scopes.
El sobre
{
"error": {
"type": "validation_error",
"code": "invoice.nif_receptor_invalid",
"message": "NIF del receptor no tiene formato válido",
"param": "partner.id_num"
}
}| Campo | Descripción |
|---|---|
type | Categoría general del error (ver tabla). |
code | Código estable, legible por máquina. Ramifica sobre este. |
message | Texto orientativo para depurar. No lo parsees. |
details | Datos estructurados específicos del error. Opcional. |
param | Campo implicado, cuando aplica. Opcional. |
Categorías (type)
type | HTTP | Significado |
|---|---|---|
validation_error | 400 | El cuerpo o los parámetros no son válidos. |
authentication_error | 401 | Credencial ausente, inválida o caducada. |
permission_error | 403 | Falta el scope, o el recurso no admite la operación. |
not_found_error | 404 | El recurso no existe en tu tenant. |
conflict_error | 409 · 422 | Choque con el estado actual (duplicado, recurso en uso). También 422 al reutilizar una Idempotency-Key con otro cuerpo. |
billing_error | 402 | El plan alcanzó un límite de uso. |
verifactu_error | 422 | La operación es válida en forma pero la AEAT la rechazaría (desglose incoherente, destinatario no censado, emisión/remisión imposible). |
internal_error | 500 | Error inesperado del servicio. |
Códigos por categoría
Autenticación (401)
code | Cuándo |
|---|---|
api_key.missing | No se envió la cabecera Authorization. |
api_key.invalid | La key no existe o fue revocada. |
api_key.no_scopes | La key es válida pero no tiene ningún scope asignado. |
oauth.invalid_token | El access token OAuth es inválido, expiró o fue revocado. |
Permisos (403)
code | Cuándo |
|---|---|
api_key.insufficient_scopes | La key no tiene el scope que exige el endpoint. |
oauth.insufficient_scopes | El token OAuth no tiene el scope requerido. |
mutability.invoice_locked | Intentas editar/borrar una factura que no está en borrador. |
plan_permission_required | El plan del tenant no incluye la funcionalidad. |
Límite del plan (402)
plan_limit_reached incluye en details la metric, plan_code, limit, used y, para métricas mensuales, reset_at. No reintentes la misma operación hasta liberar stock, empezar un nuevo mes o cambiar de plan.
Validación (400)
code | Cuándo |
|---|---|
invalid_body | El JSON no valida contra el esquema del endpoint. |
param.invalid_uuid | Un UUID de la ruta está mal formado. |
pagination.invalid_cursor | El cursor no es un cursor válido. |
invoices.partner_required | El tipo de factura exige partner_id y no se envió. |
idempotency.key_too_long | La cabecera Idempotency-Key supera los 255 caracteres. |
No encontrado (404)
invoice.not_found, invoice_line.not_found, partner.not_found, product.not_found, serie.not_found, tax.not_found, economic_activity.not_found, verifactu_record.not_found, external_invoice.not_found.
Conflicto (409)
code | Cuándo |
|---|---|
partner.external_id_taken | Otro partner ya usa ese external_id. |
partner.id_num_taken | Otro partner ya usa ese NIF/identificación. |
product.external_id_taken | Otro producto ya usa ese external_id. |
product.name_taken | Otro producto ya usa ese nombre. |
serie.code_taken | Ya existe una serie con ese code. |
serie.in_use | La serie tiene facturas o presupuestos (borradores incluidos) y no se puede borrar. |
serie.numbering_started | La serie ya numeró un documento: su numeración queda fijada, solo cambian name e is_active. |
serie.type_immutable | El type se fija al crear la serie y no se puede cambiar. |
serie.last_of_type | No puedes borrar la última serie de un tipo. |
economic_activity.in_use | La actividad está referenciada por facturas. |
economic_activity.last_activity | No puedes borrar la última actividad del tenant. |
idempotency.request_in_progress | Otra petición con la misma Idempotency-Key sigue procesándose. Reintenta luego. |
issuance_authorization_required | La cuenta todavía no tiene habilitada la emisión. |
external_invoices_not_enabled | La cuenta no tiene habilitado Verifactu Direct API. |
Idempotencia: cuerpo reutilizado
Reutilizar una Idempotency-Key con un cuerpo distinto devuelve 422 con code: idempotency.key_reused_with_different_body. Usa una clave nueva por operación. Ver Convenciones → Idempotencia.
Verifactu / no procesable (422)
type: verifactu_error. La petición es válida en forma pero la AEAT la rechazaría, así que se corta en origen: desglose incoherente, factura simplificada que supera el límite, o —con validar_cliente— un NIF de destinatario no identificado en el censo de la AEAT. El message describe la causa concreta.
Reglas de inmutabilidad
serie.in_use y mutability.invoice_locked reflejan la misma idea: lo que ya es un documento fiscal (factura emitida, serie con numeración usada) no se reescribe. Ver Ciclo de vida y Series.
Manejo recomendado
- Ramifica sobre
error.code, nunca sobremessage. - En
401/403, revisa la key y sus scopes; no reintentes con la misma. - En
409, el estado del servidor ya no admite tu petición: léelo y reconcilia (p. ej. anteexternal_id_taken, usa elPUTde upsert). - En
422al emitir, la factura sigue en borrador: corrige y reintenta. - Con
Idempotency-Key:409 idempotency.request_in_progress→ reintenta en unos segundos;422 idempotency.key_reused_with_different_body→ usa una clave nueva por operación (ver Idempotencia).