Errores
Envoltorio de error normalizado, catálogo de type y code con anclas estables, y estrategia de reintentos.
Toda respuesta de error de la API pública usa un envoltorio JSON
consistente. El estado HTTP indica la categoría general; el campo type
desambigua y el campo code apunta a la causa específica.
Envoltorio
{
"error": {
"type": "invalid_request_error",
"code": "parameter_invalid",
"message": "El campo client_id es obligatorio.",
"param": "client_id",
"request_id": "req_01HKQS5N8VR7QXJ9K3T6BWPMZA",
"doc_url": "https://docs.factuarea.com/guides/errors#parameter_invalid"
}
}Campos:
type— categoría general del error. Estable y enumerada (lista abajo).code— causa específica. Estable y enumerada.subcode— opcional. Presente en algunas respuestas409 conflict_error/resource_already_existspara señalar la clave duplicada exacta — p. ej.subcode: "tax_id_already_exists".message— texto para personas en español. No se garantiza estable entre versiones; útil para logging y visualización.param— opcional, presente en errores de validación. Apunta al campo problemático.doc_url— opcional. Enlace a esta guía con ancla alcodeespecífico (#{code}).request_id— identificador único de la petición (req_<ULID>). Inclúyelo siempre cuando contactes con soporte. También se devuelve en el header de respuestaX-Request-Id.
El objeto error siempre lleva type, code y message; el resto
de campos están presentes cuando es relevante.
Tipos de error
| type | HTTP | Descripción |
|---|---|---|
invalid_request_error | 400 o 422 | Payload malformado, parámetros faltantes/inválidos o fallo de validación de negocio. |
authentication_error | 401 | La API key falta, es inválida, está revocada, ha expirado o la IP no está en la lista de acceso. |
authorization_error | 403 | La key es válida pero el scope no cubre el endpoint. |
permission_error | 403 | El plan/add-on no da acceso a la funcionalidad. |
not_found_error | 404 | El recurso solicitado no existe o no pertenece a la empresa de la key. |
conflict_error | 409 | Conflicto de creación, lock de idempotencia o recurso duplicado (p. ej. un tax_id ya registrado). |
idempotency_error | 409 | Reutilización de Idempotency-Key con un payload distinto. |
rate_limit_error | 429 | Superada la cuota por minuto o mensual, o demasiados fallos de autenticación. |
api_error | 500 | Error inesperado del backend. Los reintentos pueden ayudar; reporta a soporte con el request_id. |
service_unavailable_error | 503 | API pública deshabilitada vía kill-switch, o caída de una dependencia (Stripe, mailer). |
Las violaciones de reglas de negocio (transición de estado inválida, una acción no
permitida en el estado actual del documento) responden 422 con
type: invalid_request_error y code: invalid_status_transition —
no 409. 409 conflict_error se reserva para creación duplicada,
conflictos de idempotencia y locks de concurrencia.
Catálogo de codes
El ancla de cada encabezado H3 coincide exactamente con el valor del campo
code del envoltorio. El doc_url que devuelve la API resuelve a la sección
específica. La lista de abajo cubre los codes que encontrarás en la práctica;
la referencia OpenAPI en vivo documenta los codes exactos por endpoint.
invalid_request_error
parameter_invalid
Un parámetro de la petición falta o es inválido. param apunta al
campo problemático (p. ej. client_id, lines[0].quantity).
parameter_invalid_format
El formato de un valor es incorrecto para su semántica (regex, longitud, codificación, un UUID malformado, una fecha fuera de formato).
parameter_invalid_range
Un valor numérico o de fecha está fuera del rango permitido (p. ej. limit
fuera de 1..100).
parameter_invalid_cursor
El cursor starting_after / ending_before no es un id de recurso
válido. Consulta Paginación.
parameter_unknown
El body contiene un campo no documentado (en endpoints estrictos).
invalid_param_format
Falló una restricción de formato en un campo tipado — p. ej. el header
Idempotency-Key o el header Factuarea-Version está malformado.
invalid_param_value
El valor no cumple una restricción (enum, formato, regla semántica).
invalid_period
El periodo de reporte solicitado es inválido (p. ej. un trimestre/año que no existe).
invalid_status_transition
La transición solicitada está prohibida por la máquina de estados del documento
(p. ej. enviar una factura que no está en un estado enviable). Las violaciones
de reglas de negocio como esta son 422, no 409.
invoice_already_paid
mark-paid sobre una factura ya pagada.
quote_already_accepted
Acción que entra en conflicto con un presupuesto ya aceptado.
business_rule_violation
Una invariante de dominio bloqueó la operación. El subcode identifica la
regla y param el campo infractor. Lo usa el ledger de pagos
(Registrar pagos):
payment_exceeds_pending_amount(param: "amount") — el importe del pago es mayor que el saldo pendiente de la factura. Aplica tanto aPOST /v1/invoices/{id}/paymentscomo aPOST /v1/purchase_invoices/{id}/payments.invalid_payment_date(param: "paid_on") — la fecha de pago cae fuera de la ventana permitidafecha_emisión … hoy(facturas de compra).purchase_invoice_not_payable(param: "status") — la factura de compra está cancelada y ya no admite pagos.
{
"error": {
"type": "invalid_request_error",
"code": "business_rule_violation",
"subcode": "payment_exceeds_pending_amount",
"message": "El importe del pago (1.500,00 €) supera el importe pendiente de la factura (710,00 €).",
"param": "amount",
"doc_url": "https://docs.factuarea.com/guides/errors#business_rule_violation",
"request_id": "req_..."
}
}unsupported_format
El formato de exportación/reporte solicitado no está soportado.
insufficient_data_for_report
No hay datos suficientes para generar el reporte de impuestos solicitado.
signature_payload_too_large
La imagen de firma del albarán supera el tamaño máximo.
authentication_error
missing_api_key
No hay header de autenticación presente (Authorization: Bearer o
X-API-Key).
invalid_api_key
La key no existe o el secreto no coincide con el hash almacenado.
api_key_revoked
La key fue revocada. Crea una nueva en el dashboard.
too_many_auth_failures
Se han limitado fallos de autenticación repetidos desde tu cliente. Espera (back off) y verifica tus credenciales.
authorization_error
insufficient_scope
La key no tiene el scope que requiere el endpoint. Consulta el catálogo en Autenticación › Scopes.
permission_error
feature_not_available_in_plan
El plan actual no incluye el módulo requerido (p. ej.
recurring_invoices).
addon_not_active
El add-on developer_api está inactivo o fuera de su periodo de gracia.
not_found_error
resource_not_found
El recurso no existe o no pertenece a tu empresa.
tax_report_not_found
El reporte de impuestos solicitado no existe.
conflict_error
resource_already_exists
Intento de crear un duplicado (p. ej. un tax_id ya registrado). El
subcode (p. ej. tax_id_already_exists) señala la clave duplicada.
resource_conflict
La operación entra en conflicto con el estado actual del recurso (p. ej. una modificación concurrente).
max_api_keys_exceeded
La empresa ha alcanzado su número máximo de API keys activas.
idempotency_error
idempotency_key_reused
Mismo Idempotency-Key, body de petición distinto. Usa una key nueva. Consulta
Idempotencia.
rate_limit_error
rate_limit_exceeded
Superaste la cuota por minuto o mensual de tu tier. El header
Retry-After indica los segundos a esperar. Consulta
Límites de peticiones.
api_error
internal_error
Error inesperado. Ya está capturado por nuestra parte, pero comparte el request_id
con soporte.
service_unavailable_error
service_unavailable
La API pública no está disponible temporalmente — deshabilitada globalmente vía kill-switch, en una ventana de mantenimiento, o una dependencia (base de datos, mailer, Stripe) no está sana. Reintenta tras un back-off corto.
Errores tipados con el SDK oficial
Los SDKs de TypeScript y PHP mapean este envoltorio a una jerarquía
de excepciones tipada, así ramificas según una clase (y lees code, type,
param, request_id) en vez de parsear JSON. Tu API key nunca se
incluye en ninguna excepción.
import {
FactuareaError,
ValidationError,
RateLimitError,
} from "@factuarea/sdk";
try {
await factuarea.invoices.create(body);
} catch (error) {
if (error instanceof ValidationError) {
console.error(error.fields); // { client_id: ["obligatorio"], … }
} else if (error instanceof RateLimitError) {
console.error(error.retryAfter); // seconds to wait
} else if (error instanceof FactuareaError) {
console.error(error.code, error.type, error.requestId);
}
}La jerarquía también exporta AuthenticationError, NotFoundError,
ConflictError, ServerError y ConnectionError.
use Factuarea\Sdk\Models\Errors\ErrorThrowable;
try {
$factuarea->invoices->publicApiV1InvoicesCreate($body);
} catch (ErrorThrowable $e) {
$error = $e->container->error;
echo $error->type->value; // e.g. "invalid_request_error"
echo $error->code; // e.g. "parameter_invalid"
echo $error->param; // e.g. "client_id"
echo $error->requestId; // quote this to support
}Consulta SDKs › Gestión de errores para ver la jerarquía completa. La política de reintentos de abajo la aplican automáticamente ambos SDKs.
request_id y soporte
Toda respuesta incluye un request_id. Adjúntalo a cualquier ticket o
petición a support@factuarea.com:
Subject: 422 on POST /v1/invoices — request_id req_01JBVH7K9Y4N3CDQ2EHJB1AGSVCon el request_id correlacionamos logs, métricas y trazas para
investigar rápido.
Estrategia de reintentos
4xxexcepto429→ no reintentes: el error está en la petición. Corrígelo y reenvía.429→ respeta el headerRetry-After. Implementa back-off exponencial con jitter.5xx→ back-off exponencial (2^n * 100ms) con jitter, máximo 5 intentos.
Stripe publica un patrón canónico que también aplica aquí: stripe.com/docs/error-handling.