Gestión de 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 cuando elcodepor sí solo es ambiguo: en los conflictos de duplicación409señala la clave duplicada exacta (p. ej.subcode: "tax_id_already_exists"), y en los errores de pago402señala qué gate rechazó la llamada (p. ej.subcode: "webhooks_addon_required"). Como elcode, es estable e invariante entre idiomas y versiones de la API.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 primer campo problemático. En errores de validación de varios campos el conjunto completo está enerrors[](ver abajo).errors[]— opcional, presente en errores de validación422. Lista todos los campos fallidos (ver Errores de validación de varios campos).details— opcional. Llevaexisting_resource_iden los conflictos de duplicación409(ver Conflictos de duplicación) ypayment_setup_urlen los errores402que necesitan un método de pago configurado (ver payment_required_error).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.
Errores de validación de varios campos
Un error de validación 422 reporta todos los campos fallidos, no
solo el primero. Los param/message planos siguen reflejando el primer
campo (por retrocompatibilidad), y errors[] lleva un item por campo
fallido — así corriges todos en una sola petición en vez de una petición
por campo.
{
"error": {
"type": "invalid_request_error",
"code": "invalid_param_value",
"message": "El campo client_id es obligatorio.",
"param": "client_id",
"errors": [
{ "param": "client_id", "code": "parameter_missing", "message": "El campo client_id es obligatorio." },
{ "param": "issue_date", "code": "parameter_invalid_format", "message": "El formato de la fecha no es válido.", "expected_format": "YYYY-MM-DD" },
{ "param": "status", "code": "parameter_invalid_enum", "message": "El valor no es válido.", "allowed_values": ["draft", "sent", "paid"] }
],
"request_id": "req_01HKQS5N8VR7QXJ9K3T6BWPMZA",
"doc_url": "https://docs.factuarea.com/guides/errors#invalid_param_value"
}
}Cada item de errors[] lleva:
param— el nombre del campo fallido.code— un código estable y machine-readable derivado de la regla de validación fallida (p. ej.parameter_missing,parameter_invalid_format,parameter_invalid_enum,parameter_invalid_integer).message— descripción legible del error del campo.expected_format— opcional. Presente solo en errores de formato; el patrón esperado (p. ej.YYYY-MM-DD,uuid,email,url).allowed_values— opcional. Presente solo en errores de enum; la lista de valores legales.
errors[] es puramente aditivo — las integraciones que solo leen param,
code y message siguen funcionando sin cambios.
Conflictos de duplicación
Un conflicto de duplicación 409 puede incluir details.existing_resource_id si la operación lo proporciona. Trátalo como opcional. Los conflictos fiscales de contactos canónicos devuelven resource_already_exists con subcode: tax_id_already_exists; un conflicto de referencia externa puede devolver external_id_already_exists. Ninguno garantiza el UUID del recurso existente en el error.
{
"error": {
"type": "conflict_error",
"code": "resource_already_exists",
"subcode": "tax_id_already_exists",
"message": "Ya existe un contacto con esa identidad fiscal en la empresa.",
"request_id": "req_01HKQS5NKW1C6W9T4G5HAIBZVM",
"doc_url": "https://docs.factuarea.com/guides/errors#resource_already_exists"
}
}Si se proporciona el UUID del recurso existente, recupéralo con su operación GET documentada. Para contactos sin ese detalle, usa POST /v1/companies/{company}/contacts/find-by-tax-id o POST /v1/companies/{company}/contacts/find-by-external-id y revisa la identidad encontrada antes de actualizar o asignar un rol. Las consultas quedan limitadas a la empresa que nombra el segmento {company}.
Detalles del problema — Problem Details (RFC 9457)
Envía Accept: application/problem+json para recibir el mismo error como
un documento Problem Details de RFC 9457
con Content-Type: application/problem+json. Con
Accept: application/json, Accept: */* o sin header Accept obtienes
el envoltorio plano de arriba.
{
"type": "https://docs.factuarea.com/errors/resource_already_exists",
"title": "Resource already exists",
"status": 409,
"detail": "Ya existe un contacto con esa identidad fiscal en la empresa.",
"instance": "/v1/companies/{company}/contacts",
"code": "resource_already_exists",
"subcode": "tax_id_already_exists",
"request_id": "req_01HKQS5NKW1C6W9T4G5HAIBZVM"
}type— la página de documentación de esecodeconcreto, por ejemplohttps://docs.factuarea.com/errors/resource_already_exists. Elcodees el único segmento variable, así que puedes construir y comparar la URI por tu cuenta. Antes era una sola URI compartida por todos los problemas: si comparas contra ella, compara mejor contracode, que no se mueve nunca.title— un resumen humano breve del tipo de problema.status— el código de estado HTTP.detail— el mensaje legible.instance— el path del recurso afectado.
La variante problem+json no descarta ningún dato extendido: code,
subcode, param, errors[], details, doc_url y request_id se
conservan como miembros de extensión RFC 9457.
Mensajes localizados
El message (y el detail de problem+json) se localiza vía el header
Accept-Language para los códigos del catálogo estable. Los idiomas
soportados son es, en y ca, con fallback a es cuando el header
está ausente o pide un idioma no soportado. El code y el subcode son
invariantes entre idiomas — ramifica siempre por code, nunca por
message.
Accept-Language: en → mensaje en inglés
Accept-Language: ca-ES → mensaje en catalán
(ausente / Accept-Language: de) → mensaje en español (fallback)Los mensajes dinámicos emitidos por excepciones de dominio quedan en español; solo se localizan los mensajes del catálogo estable.
Tipos de error
| type | HTTP | Descripción |
|---|---|---|
invalid_request_error | 400, 405, 411, 413, 415 o 422 | Payload malformado o demasiado grande, método o tipo de medio no soportado, parámetros faltantes/inválidos o fallo de validación de negocio. |
authentication_error | 401 o 429 | La API key falta, es inválida, está revocada, ha expirado o la IP no está en la lista de acceso; 429 (too_many_auth_failures) cuando fallan demasiados intentos de autenticación. |
payment_required_error | 402 | Falta un requisito comercial: falló un cargo, no hay método de pago configurado, no hay un plan al que facturar o el plan actual concede capacidad cero para la operación. |
authorization_error | 403 o 422 | La key es válida pero la operación no está abierta para ella: el scope no cubre el endpoint, una key de pruebas intenta cambiar una cuenta real, el nivel de acceso es de solo lectura, la operación pertenece a otra cuenta o el plan de la empresa no incluye la funcionalidad. Un tope de tu plan (keys, endpoints de webhook) o un scope que no admite responde 422. |
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, modificación concurrente o recurso duplicado (p. ej. un tax_id ya registrado). |
idempotency_error | 422 o 409 | Reutilización de Idempotency-Key con un payload distinto (422, idempotency_key_reused) o mientras su primera petición sigue en curso (409, idempotency_key_in_use). |
rate_limit_error | 429 | Superada una cuota por minuto o mensual, u otro presupuesto de la API. |
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). |
402 y 403 no son intercambiables. Un 402
(payment_required_error) significa que la operación está a tu alcance
y lo único que se interpone es el dinero: configura un método de pago,
resuelve el cargo denegado, o contrata el plan o el add-on al que se
factura. Un 403 significa que el acceso en sí está denegado —o la key
no tiene el scope o el plan de la empresa no incluye la funcionalidad
(los dos son authorization_error)— y ningún reintento de
pago lo cambia. La pareja addon_required (402, el plan actual concede
capacidad cero para la operación) y addon_not_active (403, no hay un
plan activo válido) es la que conviene leer dos veces. El gate del NIF
adicional traza la misma línea: additional_nif_plan_required (402, no hay
un plan vigente al que facturar) y additional_nif_not_in_plan (403, el
plan está vigente pero no incluye NIF adicionales).
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
y conflictos de concurrencia; una Idempotency-Key todavía en uso es
409 idempotency_error.
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.
Para la referencia completa de cada error code agrupado por bounded
context, con su estado HTTP y type, consulta Todos los error codes.
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).
Página de referencia: parameter_invalid.
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).
Página de referencia: parameter_invalid_format.
parameter_invalid_range
Un valor numérico o de fecha está fuera del rango permitido (p. ej. limit
fuera de 1..100).
Página de referencia: parameter_invalid_range.
parameter_invalid_cursor
El cursor starting_after / ending_before no es un id de recurso
válido. Consulta Paginación.
Página de referencia: parameter_invalid_cursor.
parameter_unknown
La petición lleva un parámetro que el endpoint no acepta: un campo de body no
documentado en un endpoint estricto, un filtro fuera de su allowlist, un campo
de sort no ordenable, el page de paginación por offset —v1 pagina por
cursor— o una cabecera retirada de la v1. param nombra lo que sobra y
subcode distingue la causa: la cabecera retirada X-Active-Profile devuelve
subcode: retired_active_profile_header. Consulta
Actuar en nombre de una empresa.
Página de referencia: parameter_unknown.
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.
Página de referencia: invalid_param_format.
invalid_param_value
El valor no cumple una restricción (enum, formato, regla semántica).
Página de referencia: invalid_param_value.
invalid_period
El periodo de reporte solicitado es inválido (p. ej. un trimestre/año que no existe).
Página de referencia: invalid_period.
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.
Página de referencia: invalid_status_transition.
invoice_already_paid
mark-paid sobre una factura ya pagada.
Página de referencia: invoice_already_paid.
quote_already_accepted
Acción que entra en conflicto con un presupuesto ya aceptado.
Página de referencia: quote_already_accepted.
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/companies/{company}/invoices/{id}/paymentscomo aPOST /v1/companies/{company}/purchase-invoices/{id}/payments.invalid_payment_date(param: "paid_on") — la fecha de un pago de gasto (POST /v1/companies/{company}/purchase-invoices/{id}/payments) es posterior a hoy. Una fecha anterior a la de emisión se acepta como pago anticipado. Las demás operaciones que reciben una fecha de pago responden de otra forma a una fecha futura; consulta Pagos anticipados y validación de la fecha de pago.purchase_invoice_not_payable(param: "status") — el gasto está cancelado y ya no admite pagos.max_webhook_endpoints_reached— se alcanzó el tope de endpoints de webhook de empresa derivado del plan. Elimina uno sin uso o cambia a un plan con un tope superior. Los endpoints de cuenta tienen su propio tope, que respondeaccount_webhook_endpoint_limit_reached.webhook_secret_recently_rotated— el secreto de firma de un endpoint de webhook de empresa o de cuenta se rotó hace menos de cinco minutos. Es un422, no un límite de peticiones: espera, despliega el secreto nuevo en tu receptor y rota de nuevo.additional_nif_limit_reached— añadir el NIF dejaría la cuenta por encima de su tope de NIF adicionales. Se comprueba antes de cualquier cargo. Consulta NIF adicionales.offer_package_loss_unconfirmed(param: "accept_offer_package_loss") — reducir las plazas contratadas, o cancelar el add-on, rompería un paquete de oferta aplicado y no confirmaste la pérdida.detailsllevaproduct,package_id,name,effective_atylines, cada línea conconcept,from_price,to_priceeinterval. Reenvía la petición conaccept_offer_package_loss: truepara aceptar la pérdida. REST y MCP devuelven la misma respuesta. Consulta Reducir plazas y NIF adicionales.contracted_seats_selection_required(param: "deactivate") — reducir las plazas contratadas por debajo de las ocupadas exige elegir endeactivateexactamenteocupadas - cantidadentidades activas de la cuenta. Encancelsolo se devuelve con una lista que no cuadra con todas las plazas ocupadas, porque sin lista se entienden todas.detailsllevaproduct,requested,occupied,required,selectedeinvalid. REST y MCP devuelven la misma respuesta. Consulta Reducir plazas y NIF adicionales.contracted_seats_exhausted— solo la aplicación web: rechaza crear o reactivar un empleado o un NIF cuando no queda ninguna plaza contratada libre. La API v1 y el MCP nunca lo devuelven: compran las plazas que faltan (consulta Compra automática).detailsllevaproduct,contracted,occupied,free,requiredymissing.
contracted_seats_below_minimum está retirado: el borrador del contrato lo
preveía, pero nunca llegó a emitirse. Reducir las plazas por debajo del mínimo de
una oferta ya no da error; pide confirmar la pérdida del paquete
(offer_package_loss_unconfirmed).
{
"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_..."
}
}Página de referencia: business_rule_violation.
unsupported_format
El formato de exportación/reporte solicitado no está soportado.
Página de referencia: unsupported_format.
insufficient_data_for_report
No hay datos suficientes para generar el reporte de impuestos solicitado.
Página de referencia: insufficient_data_for_report.
signature_payload_too_large
La imagen de firma del albarán supera el tamaño máximo.
Página de referencia: signature_payload_too_large.
managed_account_plan_not_self_serve
Pediste el plan enterprise para una cuenta gestionada. Una cuenta gestionada se
crea o se cambia en emprendedor o empresario; enterprise solo lo asigna
Factuarea por contrato. Elige uno de los dos, o contacta con Factuarea para
acordar el contrato. Consulta
Elige el plan.
Página de referencia: managed_account_plan_not_self_serve.
managed_account_provider_in_trial
Tu cuenta está en su periodo de prueba y pidió una cuenta gestionada real, que se
cobra desde el primer día. Durante el trial solo puedes crear cuentas gestionadas
de pruebas, con una key fact_test_. Créala con una key de pruebas, o empieza una
suscripción de pago y repite el aprovisionamiento. Consulta
Tu cuenta en trial o con un problema de pago.
Página de referencia: managed_account_provider_in_trial.
managed_accounts_limit_reached
Tu cuenta ya tiene tantas cuentas gestionadas reales bajo gestión vigente como
permite su tope (max_managed_accounts). Las cuentas gestionadas de pruebas no
cuentan. Termina la gestión de una que ya no lleves, o contacta con Factuarea para
ampliar el tope. Consulta El tope.
Página de referencia: managed_accounts_limit_reached.
managed_account_not_claimable
La cuenta gestionada es una cuenta de pruebas, y una cuenta de pruebas nunca se reclama: no tiene vale de reclamación que emitir ni reemitir. Reemite el vale solo para cuentas gestionadas reales. Consulta Probar cuentas gestionadas.
Página de referencia: managed_account_not_claimable.
access_level_raise_forbidden
Intentaste subir el nivel de acceso a una cuenta gestionada que su titular ya reclamó. Antes de la reclamación el proveedor puede subir o bajar su nivel; desde la reclamación solo el titular puede subirlo, y el proveedor solo puede bajarlo. Pide al titular que lo suba desde su app, o repite la operación con un nivel igual o inferior al actual. Consulta Tu nivel de acceso.
Página de referencia: access_level_raise_forbidden.
enterprise_contract_transition_required
La cuenta gestionada tiene un contrato enterprise, y terminar su gestión trasladaría su pago al titular antes de que Factuarea haya fijado el plan con el que continúa. Nunca se crea una suscripción sin precio, así que no cambió nada y la gestión sigue vigente. Contacta con Factuarea para que fijemos el plan y repite la operación. Consulta Enterprise por contrato.
Página de referencia: enterprise_contract_transition_required.
import_file_invalid
Uno de los ficheros de la importación no se puede leer con el formato de la
plantilla: no es un CSV legible, su cabecera no aparece en sus 20 primeras líneas,
le falta una columna obligatoria o tiene una línea rota. param dice qué fichero
falla (accounts_file o customers_file) y el mensaje nombra la columna o la
línea. No se escribió nada. Un valor inválido en una fila no es este error: sale
como error de esa fila en la vista previa. Consulta
El fichero de cuentas.
Página de referencia: import_file_invalid.
import_file_too_large
Uno de los ficheros de la importación supera los 5 MB. Responde 413, y el tamaño
se comprueba antes de leer ninguna fila, así que no se procesó ni se escribió
nada. param dice qué fichero falla. Reduce el fichero, o pártelo en varias
importaciones de menos de 5 MB y aplícalas una tras otra. Consulta
Límites.
Página de referencia: import_file_too_large.
import_row_limit_exceeded
Uno de los ficheros de la importación lleva más filas de las que admite una sola
importación: 100 en accounts_file y 1.000 en customers_file. Las filas se
cuentan antes de leerlas, así que no se escribió nada. param dice qué fichero
falla. Parte el fichero y aplica las partes una tras otra: la importación se puede
repetir, así que una fila ya importada no crea nada nuevo. Consulta
Límites.
Página de referencia: import_row_limit_exceeded.
account_webhook_endpoint_limit_reached
La cuenta alcanzó el número de endpoints de webhook de cuenta que su tier permite
en ese entorno. El tope se cuenta por cuenta y por entorno (live o test),
incluye los endpoints deshabilitados y es independiente del tope de los endpoints
de empresa: solo borrar un endpoint libera un hueco. Borra uno que ya no uses o
pasa a un tier superior; un mismo endpoint puede suscribirse a varios tipos de
evento. Un tier que no permite ninguno responde, en cambio, 402
addon_required. Consulta
El tope por tier.
Página de referencia: account_webhook_endpoint_limit_reached.
authentication_error
missing_api_key
No hay header de autenticación presente (Authorization: Bearer o
X-API-Key).
Página de referencia: missing_api_key.
invalid_api_key
La key no existe o el secreto no coincide con el hash almacenado.
Página de referencia: invalid_api_key.
api_key_revoked
La key fue revocada, y una key revocada no vuelve a autenticar nunca. Crea una nueva en el dashboard.
Página de referencia: api_key_revoked.
api_key_expired
El secreto es correcto, pero la key superó su fecha de caducidad (expires_at).
Emite una key nueva; si fijas fechas de caducidad, planifica la rotación antes de
esa fecha para que la integración nunca se quede sin servicio.
Página de referencia: api_key_expired.
ip_not_allowed
La key restringe las direcciones que acepta y la petición llegó desde una fuera de esa lista. Añade la dirección de salida de tu servidor a la lista de acceso de la key, o usa una key sin restricción de IP si tu dirección cambia.
Página de referencia: ip_not_allowed.
too_many_auth_failures
Se han limitado fallos de autenticación repetidos desde tu cliente. Espera (back off) y verifica tus credenciales.
Página de referencia: too_many_auth_failures.
payment_required_error
Un 402 significa que falta un requisito comercial. La llamada puede comprar
plazas de NIF adicionales o de empleados, o cobrar una cuenta gestionada, puede
que no haya un plan al que facturarla, o que el plan actual le conceda capacidad cero. No se
puede reintentar tal cual: resuelve el pago o cambia de plan y después repite la
misma petición. Las otras dos respuestas del gate del NIF adicional son 403:
additional_nif_not_in_plan y
billing_managed_by_provider.
Nota de versión. Tres de estos códigos se publicaron antes de que
existiera esta categoría y se servían como invalid_request_error.
Llevan payment_required_error desde
Factuarea-Version: 2026-09-01 en adelante:
payment_method_required, employee_seat_payment_method_required y
employee_seat_charge_failed. Las peticiones en una versión anterior
conservan el type de siempre. Los códigos del NIF adicional
(additional_nif_plan_required y additional_nif_charge_failed) y
addon_required son posteriores y llevan payment_required_error en todas
las versiones. error.code, error.subcode y el estado 402 son idénticos
en todas las versiones: ramifica por code y no tendrás que pensar en esto.
payment_method_required
Comprar plazas de NIF adicionales las cobra de inmediato: el endpoint
PATCH /v1/accounts/{account}/additional-nif-seats, y también
POST /v1/accounts/{account}/companies, los endpoints de activación y aceptar un
traspaso de NIF en una cuenta que ya tiene titular cuando no queda plaza libre y
la llamada compra las que faltan
(Compra automática). La cuenta opera en modo real, con una
suscripción de pago y sin método de pago configurado. Aprovisionar una cuenta
gestionada de pago lo lanza igual, cuando la cuenta proveedora que la paga no
tiene método de pago. La respuesta lleva details.payment_setup_url: ábrelo,
registra una tarjeta y repite la llamada. Consulta
NIF adicionales y
Cuentas gestionadas: cuándo se cobra.
{
"error": {
"type": "payment_required_error",
"code": "payment_method_required",
"message": "La cuenta no tiene un método de pago configurado para cobrar el NIF adicional: configúralo y vuelve a intentarlo.",
"details": { "payment_setup_url": "https://billing.stripe.com/p/session/live_YWNjdF8xS2ZHM0RLb0h4RXBGV3lY" },
"request_id": "req_01HKQS5NPAYMENTMETHODREQ01",
"doc_url": "https://docs.factuarea.com/guides/errors#payment_method_required"
}
}Página de referencia: payment_method_required.
additional_nif_charge_failed
El cargo inmediato de las plazas de NIF adicional fue denegado —tarjeta rechazada, autenticación requerida o el proveedor de pago inaccesible—. Si el cargo falla, las plazas no se compran, el NIF no se añade ni se activa, y no queda ningún cargo. Arregla el método de pago en el portal de facturación y repite la misma llamada; consulta con tu banco si la tarjeta se sigue denegando.
Página de referencia: additional_nif_charge_failed.
additional_nif_plan_required
La cuenta no tiene un plan vigente, así que no hay suscripción a la que cobrar el
NIF adicional. No se cobró nada ni se escribió ningún cambio. Contrata un plan
empresario o enterprise, o reanuda el que cancelaste, y repite la misma
llamada.
Página de referencia: additional_nif_plan_required.
employee_seat_payment_method_required
Comprar plazas de empleado las cobra de inmediato: los endpoints de compra de
plazas, y el alta o la reactivación de un empleado en la v1 y el MCP cuando no
queda plaza contratada libre y la llamada compra las que faltan
(Compra automática). La empresa opera en
modo real sin método de pago configurado. El remedio es el mismo que en
payment_method_required, y la respuesta también lleva
details.payment_setup_url. Ocupar una plaza contratada libre nunca cobra.
Página de referencia: employee_seat_payment_method_required.
employee_seat_charge_failed
El cargo prorrateado de las plazas de empleado fue denegado. Las plazas no se compran, así que el empleado no se crea ni se activa cuando la llamada compraba las que faltan. Arregla el método de pago y reintenta; consulta con tu banco si la tarjeta se sigue denegando. Consulta Errores de pago.
Página de referencia: employee_seat_charge_failed.
employee_seats_not_in_plan
La cuenta tiene un plan vigente, pero no incluye el control horario (el módulo
control_horario: empresario, integral y enterprise), así que no se pueden
comprar plazas de empleado. Es un límite de plan y no un pago pendiente, por eso es
un 403 y no un 402. No se cobró nada ni se escribió ningún cambio; bajar o
mantener las plazas contratadas sigue permitido. Pasa la cuenta a Profesional o
superior y repite la misma llamada. Consulta
Errores de pago.
Página de referencia: employee_seats_not_in_plan.
addon_required
El tier derivado del plan actual permite cero endpoints de webhook, por lo
que POST /v1/companies/{company}/webhook-endpoints devuelve 402 con el subcode heredado
webhooks_addon_required. Cambia a un plan que incluya webhooks y repite la
llamada. No falta un scope ni existe un add-on Developer API independiente
que se pueda contratar. POST /v1/accounts/{account}/webhooks responde el mismo
402 con subcode: account_webhooks_addon_required cuando el tier del pagador
de la cuenta no admite ningún endpoint de cuenta; cuando admite alguno, alcanzar
el tope es un 422:
account_webhook_endpoint_limit_reached.
Página de referencia: addon_required.
authorization_error
insufficient_scope
La key no tiene el scope que requiere el endpoint. Consulta el catálogo en
Autenticación › Scopes. El super-scope * no
cubre los scopes privilegiados accounts:read y accounts:write: una key que
solo tenga * responde insufficient_scope en una operación de provisión.
Consulta el super-scope.
Página de referencia: insufficient_scope.
privileged_scope_required
La operación exige un scope privilegiado (accounts:read o accounts:write) y no
se cumple: o la cuenta de la key no tiene concedido el privilegio de proveedor de
cuentas, o la persona que emitió la key ya no es titular ni administradora de la
cuenta. Las dos causas responden igual, a propósito. Pide a Factuarea el
privilegio para tu cuenta, o emite una key nueva con una persona que sea titular o
administradora y repite la operación. Consulta
Scopes privilegiados.
Página de referencia: privileged_scope_required.
live_credential_required
Una key de pruebas (fact_test_) intentó cambiar una cuenta real. Solo ocurre en
las operaciones que cambian la propia cuenta (equipo, invitaciones, titularidad,
traspasos de NIF, cartera de NIF, keys y plazas): una key de pruebas no puede
alterar datos reales. Repite la operación con una key real (fact_live_) de esa
cuenta; las lecturas siguen disponibles con la key que ya usas. Consulta
Probar cuentas gestionadas.
Página de referencia: live_credential_required.
company_read_only
El nivel de acceso efectivo al NIF es view, así que la key puede leerlo pero no
escribir, y no se creó ni cambió nada. El nivel es el menor entre el guardado en la
key y el que tiene hoy la persona que la emitió: un grant view en la cuenta, o un
acceso cedido view de otra cuenta. Limita la
integración a lecturas sobre ese NIF, o pide a quien gestiona ese acceso que lo
suba a operate y emite la key de nuevo. Consulta
El techo de nivel por NIF.
Página de referencia: company_read_only.
forbidden_action
La acción se rechaza aunque el scope sea el correcto. error.message dice qué
caso aplica:
- La key alcanza el NIF por una relación entre cuentas —un acceso cedido o una cuenta gestionada— y la operación es de la cuenta dueña del NIF: los registros de peticiones, los eventos, los endpoints de webhook de empresa, la facturación de la cuenta, el consumo de automatizaciones y las escrituras de las credenciales de pago conectadas. Una relación nunca las abre, con cualquier nivel y también en lecturas. Usa una credencial de la cuenta dueña.
- El NIF es de una cuenta gestionada cuyo titular ya la paga: su proveedor puede leerlo y editar sus datos, pero es el titular quien decide cuándo activarlo, desactivarlo o archivarlo.
- La persona que emitió la key no es titular ni administradora de la cuenta, o la key no alcanza todos los NIF de la cuenta, en una operación de la propia cuenta.
Consulta Resolución y errores.
Página de referencia: forbidden_action.
company_inactive
La empresa que nombra el segmento {company} de la ruta está dentro del ámbito de
la credencial, pero su NIF no está active: su estado es inactive (desactivado)
o archived, así que no se puede operar. Un NIF inactive conserva todos sus
datos y se puede reactivar; uno archived es
terminal. suspension_reason dice por qué el sistema suspendió un NIF, y vale
null cuando lo desactivó la propia cuenta. Reactiva el NIF, o apunta la ruta a
otra empresa de tu ámbito. Consulta
El estado de un NIF y
Resolución y errores.
Página de referencia: company_inactive.
additional_nif_not_in_plan
La cuenta tiene un plan vigente, pero no incluye NIF adicionales: solo
empresario y enterprise (trial incluido) los admiten. Es un límite del plan y
no un pago pendiente, por eso es un 403 y no un 402. No se cobró nada ni se
escribió ningún cambio. Pasa la cuenta a uno de esos planes y repite la misma
llamada. Consulta El gate y sus cuatro códigos.
Página de referencia: additional_nif_not_in_plan.
billing_managed_by_provider
Otra cuenta paga la facturación de esta cuenta (es una cuenta gestionada que su proveedor paga), así que no puede comprar por su cuenta un NIF adicional ni una plaza de empleado. No se cobró nada ni se escribió ningún cambio. Pide a la cuenta que paga que añada, active o acepte el NIF. Consulta Lo que el cliente no puede comprar mientras pagas.
Página de referencia: billing_managed_by_provider.
max_api_keys_exceeded
La empresa ha alcanzado su número máximo de API keys activas. Responde 422, como los demás topes de tu plan.
Página de referencia: max_api_keys_exceeded.
feature_not_available_in_plan
El plan actual no incluye el módulo requerido (p. ej.
recurring_invoices).
Página de referencia: feature_not_available_in_plan.
addon_not_active
La empresa no tiene un plan de Factuarea activo que incluya acceso a la API pública — por ejemplo, el trial de 10 días caducó o la suscripción venció fuera de su periodo de gracia. Contrata o renueva un plan para seguir usando la API.
Página de referencia: addon_not_active.
not_found_error
resource_not_found
El recurso no existe o no pertenece a tu empresa.
Página de referencia: resource_not_found.
profile_not_found
El segmento {company} de la ruta nombra una empresa que no existe o que no
está en el ámbito de NIFs de la credencial. Ambos casos responden igual para que
la API nunca revele empresas de otras cuentas. Usa el id de una de las empresas
que lista GET /v1/accounts/{account}/companies; el identificador viaja en la
ruta, no en ninguna cabecera.
Página de referencia: profile_not_found.
tax_report_not_found
El reporte de impuestos solicitado no existe.
Página de referencia: tax_report_not_found.
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.
Página de referencia: resource_already_exists.
resource_conflict
La operación entra en conflicto con el estado actual del recurso (p. ej. una modificación concurrente).
Página de referencia: resource_conflict.
provisioning_account_claimed
El titular ya reclamó esta cuenta gestionada y la lleva por su cuenta: ya no se
puede reclamar, reemitir su vale de reclamación ni aprovisionar de nuevo con el
mismo external_ref. Comprueba su estado en lugar de reintentar; para otra cuenta
gestionada, usa un external_ref distinto. Consulta
La reclamación.
Página de referencia: provisioning_account_claimed.
managed_account_external_ref_in_other_environment
El external_ref ya lo usa una cuenta gestionada del otro entorno (live o
test). Es único en los dos, y la respuesta no nombra esa cuenta porque este
entorno no la lista. No se escribió nada. Usa un external_ref distinto, por
ejemplo con un prefijo diferente en cada entorno. Consulta
Idempotencia.
Página de referencia: managed_account_external_ref_in_other_environment.
import_in_progress
Ya hay una importación masiva de tu cuenta en curso. Las importaciones de una misma cuenta se aplican de una en una, para que ninguna cuenta, cargo ni NIF se cree dos veces; esta no se aplicó y no se escribió nada. Espera a que termine la importación en curso y repite esta con el mismo fichero: se puede repetir, y las filas que creó la otra importación salen como ya existentes, sin cuentas ni cargos nuevos. Consulta Una importación a la vez.
Página de referencia: import_in_progress.
idempotency_error
idempotency_key_reused
Mismo Idempotency-Key, body de petición distinto. Usa una key nueva. Consulta
Idempotencia.
Página de referencia: idempotency_key_reused.
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.
Página de referencia: rate_limit_exceeded.
api_error
internal_error
Error inesperado. Ya está capturado por nuestra parte, pero comparte el request_id
con soporte.
Página de referencia: internal_error.
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.
Página de referencia: service_unavailable.
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/companies/{company}/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.