Saltar al contenido principal

Errores genéricos

Para desarrolladores

Los códigos de error comunes a toda la API, con el status HTTP y el significado de cada uno. Dos casos llevan un code específico en lugar del genérico:

  • 409 por una regla de negocio: el code nombra la regla incumplida (sandbox_relay_restricted, field_key_already_exists, ...).
  • 404 por un recurso inexistente: el code es <recurso>_not_found.

Sumar códigos nuevos o más específicos no rompe una integración existente. Reemplazar un código específico por uno más genérico, en cambio, sí la rompería.

bad-request​

code: bad_request · HTTP 400. Request sintácticamente válido pero rechazado antes de la validación de esquema (p. ej. parámetro de query mal formado). El detail es descriptivo.

unauthorized​

code: unauthorized · HTTP 401. Falta el Authorization, el token es inválido o expiró, o la API key es desconocida/revocada. detail es genérico ("Authentication required.") y no distingue token inválido de expirado.

Un X-Instance-Slug que no corresponde a ninguna instancia también produce un 401: la respuesta es idéntica en status, body y headers a la de una API key inválida, de modo que no revela qué slugs existen. Si recibe un 401 inesperado, revise tanto la API key como el slug.

Todo 401 incluye el header WWW-Authenticate: Bearer, sin los parámetros realm ni error (RFC 6750 §3.1). Si necesita que soporte revise el caso, indique el X-Request-Id de la respuesta.

forbidden​

code: forbidden · HTTP 403. El request está autenticado pero no autorizado: el scope es insuficiente, el rol no corresponde o la instancia de destino está deshabilitada. Una API key nunca opera una instancia deshabilitada. El detail es genérico ("Access denied.") y no informa el motivo. La denegación por dirección de origen tiene su propio code (ver ip-not-allowed).

ip-not-allowed​

code: ip_not_allowed · HTTP 403. La dirección de origen del request no está permitida. Lo emiten los dos controles de IP: la lista de IPs permitidas de la instancia y la de la API key.

El detail es el mismo texto genérico de cualquier 403 ("Access denied."): solo el code lo distingue. No se informa cuál de los dos controles rechazó el request, ni las direcciones o rangos configurados.

Solo lo recibe un request ya autenticado y autorizado sobre la instancia. Un request sin credencial válida, aunque venga de una dirección no permitida, recibe el 401 o 403 genérico que corresponda, nunca este código. Así la respuesta no revela que la instancia existe ni que tiene una lista de IPs permitidas.

Qué hacer al recibirlo: revise desde qué dirección sale el tráfico y compárela con la lista de IPs permitidas de la instancia y con la de la API key en uso. Si el tráfico sale por un NAT o un proxy, el control evalúa la dirección pública de salida, no la interna del host.

not-found​

code: <recurso>_not_found · HTTP 404. El recurso no existe. El code nombra el recurso (domain_not_found para un dominio, sender_not_found para un remitente) y su type termina en <recurso>-not-found. El detail es descriptivo: "{recurso} '{id}' not found". El code genérico not_found solo aparece ante una ruta inexistente.

mail-outside-retention​

code: mail_outside_retention · HTTP 404. Lo devuelven GET /v1/mails/{mail_id} y GET /v1/mails/{mail_id}/logs cuando el identificador corresponde a una fecha anterior a la ventana de retención en línea (3 meses). El correo puede haber existido, pero ya no está disponible. Ante mail_not_found, revise el id. Ante este código, el id es válido pero demasiado antiguo para consultarlo.

method-not-allowed​

code: method_not_allowed · HTTP 405. El path existe pero no acepta el verbo HTTP usado.

conflict​

code: <regla> · HTTP 409. Violación de unicidad o de una regla de negocio. El code siempre es específico y nombra la regla incumplida. El genérico conflict no se emite.

Códigos más comunes​

codeCuándo ocurre
domain_sender_not_verifiedEl dominio del remitente no está verificado.
template_not_publicLa plantilla referida no es pública y el envío no es de prueba (test_mode: true).
template_publishedSe intentó editar una plantilla publicada. Para cambiarla, despublíquela desde el panel.
template_read_onlySe intentó editar una plantilla de un editor que ya no se ofrece: se puede leer y enviar, pero no modificar.
template_name_takenYa existe otra plantilla con ese nombre. No importan mayúsculas ni tildes (Confirmación de compra y confirmacion de compra son el mismo nombre), y el de una plantilla eliminada queda libre.
template_editor_unavailableSe intentó crear una plantilla por API en un entorno donde el editor de la plataforma no está disponible.
sandbox_relay_restrictedUn remitente sandbox intentó enviar a destinatarios fuera de sandbox.
field_key_already_existsYa existe un campo activo con ese field_key.
field_key_reservedEl field_key perteneció a un campo eliminado; queda reservado para siempre y no puede reutilizarse.
field_name_takenYa existe otro campo con ese nombre. No importan mayúsculas ni tildes (Región y region son el mismo nombre), y el de un campo eliminado queda libre.
dataset_identity_field_requiredEl dataset declara más de un canal, o ninguno, así que el canal no determina por cuál identificador reconoce a un contacto y hay que declararlo.
dataset_name_takenYa existe otro dataset con ese nombre. No importan mayúsculas ni tildes (Clientes región y clientes region son el mismo nombre), y el de un dataset eliminado queda libre.
dataset_identity_value_missingEl contacto no trae el identificador por el que ese dataset reconoce a un contacto. Una identidad parcial es válida; una ausente no.
dataset_required_column_missingEl contacto no trae una columna que el dataset declara obligatoria.
dataset_column_not_declaredEl contacto, la corrección o la búsqueda nombra una columna que el dataset no declara.
dataset_field_not_foundSe declaró como columna un campo que no existe en el catálogo.
dataset_field_duplicatedLa declaración de columnas trae dos veces el mismo campo.
dataset_identity_field_missingLa declaración de columnas deja fuera el campo por el que el dataset reconoce a un contacto.
form_dataset_columns_not_collectedEl formulario no recolecta como ítem obligatorio una columna que exige el dataset que alimenta. Ocurre al apuntarlo al dataset sin pedir que se complete (align_dataset_columns), o al quitar o volver opcional el último ítem obligatorio que recolectaba esa columna. El detalle nombra las columnas.
dataset_columns_not_collected_by_formsEl cambio vuelve obligatoria una columna que un formulario conectado al dataset no recolecta como obligatoria, y no se indicó qué hacer con esos formularios (connected_forms: align o keep). El detalle nombra cada formulario y lo que le falta.
form_name_takenYa existe otro formulario con ese nombre. No importan mayúsculas ni tildes (Suscripción newsletter y suscripcion newsletter son el mismo nombre), y el de un formulario eliminado queda libre.
list_name_takenYa existe otra lista con ese nombre. No importan mayúsculas ni tildes (Clientes Black Friday y clientes black friday son el mismo nombre), y el de una lista eliminada queda libre.
list_field_duplicatedLa declaración de columnas de una lista trae dos veces el mismo campo.
list_field_position_duplicatedLa declaración de columnas de una lista asigna la misma posición del CSV a dos campos.
list_field_not_foundSe declaró como columna de una lista un campo que no existe en el catálogo.
list_field_inactiveSe declaró como columna de una lista un campo desactivado del catálogo.
list_has_no_fieldsSe subió un archivo a una lista que no tiene columnas declaradas: sin ellas no hay cómo interpretar el archivo.
list_size_exceededLa lista superaría su tamaño máximo con el archivo entrante. Se mide sobre los datos que ya tiene más el archivo; un overwrite cuenta solo el archivo.
list_import_duplicatedEn un append, el archivo es idéntico a uno que la lista ya tiene, o a uno cuya importación sigue en curso. El detail nombra esa importación.
consent_term_inactiveSe intentó registrar un consentimiento contra un término desactivado.
consent_term_no_current_versionEl término no tiene ninguna versión vigente (is_current) contra la cual registrar el consentimiento.
consent_contact_type_channel_mismatchEl tipo de identificador no es válido para el canal de la regla. Por ejemplo, una dirección de correo en un canal push, o un teléfono en mail.
consent_duplicate_ruleUna misma llamada trae dos reglas para el mismo par canal + finalidad.
consent_contact_pairs_mismatchAl resolver el consentimiento vigente, las listas de identificadores y de tipos vienen con distinta cantidad de elementos: se emparejan por posición.

request-too-large​

code: request_too_large · HTTP 413. El request supera alguno de estos límites de tamaño:

  • El body del request supera el máximo permitido para el path.
  • El body llega comprimido y supera el máximo al descomprimirlo.
  • Un adjunto individual de multipart/form-data supera su límite.
  • El archivo de una importación de lista supera su límite.
  • El mensaje ya armado supera el tamaño máximo aceptado para envío.
  • El correo supera la capacidad de ráfaga del límite de ancho de banda de la instancia.

Si el rechazo viene del límite del body, la respuesta incluye max_bytes.

Los límites concretos están en Enviar un correo → Límites de tamaño.

unsupported-media-type​

code: unsupported_media_type · HTTP 415. El Content-Type enviado no es el esperado (típicamente, no es JSON).

validation-error​

code: validation_error · HTTP 422. Falla de validación de esquema. El body incluye errors[] (ver Errores por campo (422)).

rate-limit-exceeded​

code: rate_limit_exceeded · HTTP 429. Se superó el límite de ancho de banda de la instancia. La respuesta incluye el header Retry-After (segundos a esperar antes de reintentar).

service-unavailable​

code: service_unavailable · HTTP 503. Un servicio del que depende la operación no respondió. El error es transitorio: reintente. El code es siempre el mismo. El detail nombra el servicio cuando eso le sirve a quien llama, y es genérico cuando no:

detailEndpointQué pasó
Mail ingest temporarily unavailable.POST /v1/mails/sendEl servicio no pudo aceptar el correo tras agotar los reintentos.
Service temporarily unavailable.cualquieraUna dependencia interna no respondió.
The search took too long; narrow it down.búsqueda de contactos de un datasetLa búsqueda recorrió demasiados contactos. No es un error de la petición: acótela con un valor más preciso, o busque por el identificador exacto o por cómo empieza.

internal-error​

code: internal_error · HTTP 500. Error no manejado. El detail es un texto genérico ("Internal server error.").

Errores por campo (422)​

Un 422 agrega el array errors[], un ítem por campo que falló. Cada ítem:

CampoSignificado
pointerJSON Pointer (RFC 6901) al campo, por ejemplo /recipients/0/email. Para un error en la raíz del body, vale "".
codeEl tipo de error (missing, string_too_short, ...). Es estable: sirve como clave para traducir el mensaje, junto con params.
messageMensaje legible en inglés, de respaldo.
paramsContexto del error; solo presente cuando no está vacío. Sus valores (p. ej. { "min_length": 3 }) sirven para completar el mensaje traducido. Los valores que no son números ni textos simples llegan como texto.

Valores frecuentes de errors[].code:

codeSignificado
missingCampo requerido ausente.
string_too_short / string_too_longFuera del rango de longitud.
string_pattern_mismatchNo cumple el patrón (regex) declarado.
value_errorFalló una validación propia del campo (incluye variantes con sufijo, p. ej. value_error.<custom>).
int_parsing / bool_parsing / float_parsingEl valor no se pudo convertir al tipo esperado.
enumValor fuera del conjunto permitido.
field_value_invalidEl valor de un contacto no cumple las reglas de su campo del catálogo (opciones, largo, patrón o rango). El pointer es /values/<field_key>. Lo emiten la carga de contactos de un dataset y la corrección de un contacto.

Algunos endpoints agregan valores propios de errors[].code para reglas de su dominio (por ejemplo, invalid_document o essential_not_supported en las plantillas); cada ficha de endpoint los lista en su sección de errores.

Ejemplo:

{
"type": "https://docs.fidelizador.com/errors/validation-error",
"title": "Unprocessable Content",
"status": 422,
"detail": "The request failed validation.",
"code": "validation_error",
"trace_id": "a7f702cba572544de30d845b9560f8d7",
"errors": [
{
"pointer": "/field_context",
"code": "enum",
"message": "Input should be 'company', 'system' or 'billing'",
"params": { "expected": "'company', 'system' or 'billing'" }
}
]
}