Respuestas de error
Las tres APIs (instance-api, saascore-api, services-api) responden todos los errores con el mismo formato: RFC 9457 application/problem+json. Un cliente decide cómo reaccionar leyendo el campo code (estable y legible por máquina), no el texto de detail.
Envelope
Campos del cuerpo de error:
| Campo | Tipo | Presencia | Significado |
|---|---|---|---|
type | string (URI) | siempre | URI dereferenciable del error: https://docs.fidelizador.com/errors/<code-kebab>. Es el code con guiones. |
title | string | siempre | Frase HTTP del status (Conflict, Not Found, ...). Legible por humanos, no para máquina. |
status | number | siempre | Código HTTP, repetido en el cuerpo. |
detail | string | siempre | Mensaje en inglés (fallback para integradores y logs). Descriptivo en 4xx de cliente (404/409/422); genérico en 401/403 y 5xx (el motivo real solo va al log). El frontend nunca localiza desde detail — usa code para derivar la traducción. |
code | string | siempre | Discriminador estable. El valor sobre el que un cliente hace switch. Independiente del status y del type. |
trace_id | string | siempre | ID de traza OTel para correlación cross-servicio. Presente en todos los errores. |
errors | array | solo 422 | Lista de errores por campo (ver Errores por campo). |
Miembros extra: algunos errores agregan campos planos adicionales (p. ej.
limityupgrade_urlen el 402 de billing). Un cliente debe ignorar los campos que no conoce.
Ejemplo (409, observado):
{
"type": "https://docs.fidelizador.com/errors/domain-already-exists",
"title": "Conflict",
"status": 409,
"detail": "Domain already exists: example.com",
"code": "domain_already_exists",
"trace_id": "dbb56202f13e1c15473bc6044ede8539"
}
Toda respuesta (éxito o error) lleva además el header X-Request-Id (correlación + audit) y, cuando hay traza OTel activa, X-Trace-Id; el request_id no va en el cuerpo. Para el formato de éxito (2xx) — envelope bare / colección, status codes y paginación — ver Paginación y formato de respuesta.
Tabla resumen
El sufijo de type es siempre el code con guiones — no se repite por fila.
bad_request | todas | Petición malformada (query/body inválido). |
|---|
unauthorized | todas | Credencial ausente, inválida o expirada. |
|---|
plan_user_limit_exceeded | saascore-api | Agregar usuario supera el cupo de asientos del plan. |
|---|---|---|
plan_send_limit_exceeded | instance-api | Enviar supera la cuota de envíos del plan para el ciclo de reset del medidor (incluye limit/remaining/reset_at). |
plan_custom_domains_limit_exceeded | instance-api | Crear un dominio supera el cupo de dominios personalizados del plan. |
plan_senders_limit_exceeded | instance-api | Crear un remitente supera el cupo de remitentes del plan. |
plan_api_access_control_unavailable | instance-api | Crear o editar una API key con permisos finos (acceso scoped) pero el plan no incluye el control de acceso de API. |
subscription_inactive | saascore-apiinstance-api | Sin suscripción usable / entitlement no resoluble (fail-closed). |
forbidden | todas | Sin permiso: scope, rol, o instancia deshabilitada. |
|---|---|---|
ip_not_allowed | instance-api | La dirección de origen no está en la whitelist de la instancia ni en la de la credencial. |
<recurso>_not_found (not_found solo en ruta inexistente) | todas | El recurso no existe. |
|---|---|---|
mail_outside_retention | instance-api | El mail_id consultado es anterior a la ventana de retención en línea. |
method_not_allowed | todas | Verbo HTTP incorrecto. |
|---|
<dominio> (p. ej. domain_already_exists) | todas | Violación de unicidad o regla de negocio. Siempre específico de dominio. |
|---|
request_too_large | instance-api | La petición supera alguna cota de tamaño (cuerpo, descompresión, adjunto, mensaje o ancho de banda). |
|---|
unsupported_media_type | todas | Content-Type no soportado. |
|---|
validation_error | todas | Falla de validación de esquema (incluye errors[]). |
|---|
rate_limit_exceeded | instance-api | Límite de ancho de banda de la instancia (incluye header Retry-After). |
|---|
internal_error | todas | Error no manejado. Cuerpo siempre genérico. |
|---|
service_unavailable | instance-api | Una dependencia no respondió. El detail nombra cuál. |
|---|