Factuarea APIDevelopers

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 el code por sí solo es ambiguo: en los conflictos de duplicación 409 señala la clave duplicada exacta (p. ej. subcode: "tax_id_already_exists"), y en los errores de pago 402 señala qué gate rechazó la llamada (p. ej. subcode: "webhooks_addon_required"). Como el code, 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á en errors[] (ver abajo).
  • errors[] — opcional, presente en errores de validación 422. Lista todos los campos fallidos (ver Errores de validación de varios campos).
  • details — opcional. Lleva existing_resource_id en los conflictos de duplicación 409 (ver Conflictos de duplicación) y payment_setup_url en los errores 402 que necesitan un método de pago configurado (ver payment_required_error).
  • doc_url — opcional. Enlace a esta guía con ancla al code especí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 respuesta X-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 ese code concreto, por ejemplo https://docs.factuarea.com/errors/resource_already_exists. El code es 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 contra code, 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

typeHTTPDescripción
invalid_request_error400, 405, 411, 413, 415 o 422Payload 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_error401 o 429La 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_error402Falta 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_error403 o 422La 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_error404El recurso solicitado no existe o no pertenece a la empresa de la key.
conflict_error409Conflicto de creación, modificación concurrente o recurso duplicado (p. ej. un tax_id ya registrado).
idempotency_error422 o 409Reutilizació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_error429Superada una cuota por minuto o mensual, u otro presupuesto de la API.
api_error500Error inesperado del backend. Los reintentos pueden ayudar; reporta a soporte con el request_id.
service_unavailable_error503API 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 a POST /v1/companies/{company}/invoices/{id}/payments como a POST /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 responde account_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 un 422, 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. details lleva product, package_id, name, effective_at y lines, cada línea con concept, from_price, to_price e interval. Reenvía la petición con accept_offer_package_loss: true para 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 en deactivate exactamente ocupadas - cantidad entidades activas de la cuenta. En cancel solo se devuelve con una lista que no cuadra con todas las plazas ocupadas, porque sin lista se entienden todas. details lleva product, requested, occupied, required, selected e invalid. 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). details lleva product, contracted, occupied, free, required y missing.

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_01JBVH7K9Y4N3CDQ2EHJB1AGSV

Con el request_id correlacionamos logs, métricas y trazas para investigar rápido.

Estrategia de reintentos

  • 4xx excepto 429 → no reintentes: el error está en la petición. Corrígelo y reenvía.
  • 429 → respeta el header Retry-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.

En esta página

EnvoltorioErrores de validación de varios camposConflictos de duplicaciónDetalles del problema — Problem Details (RFC 9457)Mensajes localizadosTipos de errorCatálogo de codesinvalid_request_errorparameter_invalidparameter_invalid_formatparameter_invalid_rangeparameter_invalid_cursorparameter_unknowninvalid_param_formatinvalid_param_valueinvalid_periodinvalid_status_transitioninvoice_already_paidquote_already_acceptedbusiness_rule_violationunsupported_formatinsufficient_data_for_reportsignature_payload_too_largemanaged_account_plan_not_self_servemanaged_account_provider_in_trialmanaged_accounts_limit_reachedmanaged_account_not_claimableaccess_level_raise_forbiddenenterprise_contract_transition_requiredimport_file_invalidimport_file_too_largeimport_row_limit_exceededaccount_webhook_endpoint_limit_reachedauthentication_errormissing_api_keyinvalid_api_keyapi_key_revokedapi_key_expiredip_not_allowedtoo_many_auth_failurespayment_required_errorpayment_method_requiredadditional_nif_charge_failedadditional_nif_plan_requiredemployee_seat_payment_method_requiredemployee_seat_charge_failedemployee_seats_not_in_planaddon_requiredauthorization_errorinsufficient_scopeprivileged_scope_requiredlive_credential_requiredcompany_read_onlyforbidden_actioncompany_inactiveadditional_nif_not_in_planbilling_managed_by_providermax_api_keys_exceededfeature_not_available_in_planaddon_not_activenot_found_errorresource_not_foundprofile_not_foundtax_report_not_foundconflict_errorresource_already_existsresource_conflictprovisioning_account_claimedmanaged_account_external_ref_in_other_environmentimport_in_progressidempotency_erroridempotency_key_reusedrate_limit_errorrate_limit_exceededapi_errorinternal_errorservice_unavailable_errorservice_unavailableErrores tipados con el SDK oficialrequest_id y soporteEstrategia de reintentos
¿Te echamos una mano?Contactar con soporte