Errores
Formato del error envelope y catálogo de códigos estables
Formato estándar
La mayoría de los endpoints devuelven errores con un código estable y legible por máquina junto al mensaje:
{
"error": {
"code": "SOURCE_NOT_FOUND",
"message": "source not found"
}
}code— SCREAMING_SNAKE_CASE, estable una vez publicado. Úsalo para lógica condicional en tu integración; nunca hagasswitchsobremessage.message— texto libre en inglés, pensado para logs, no para mostrar directamente al usuario final.
Un subconjunto de endpoints — POST /agent/query, POST /agents/{id}/query y los endpoints de /tenants/me/mcp/* — todavía responden con el formato heredado, sin code:
{ "error": "missing tenant context" }Distínguelos por status HTTP y por el texto de error mientras se completa la migración a códigos estables. El resto de esta página documenta el formato con code (mayoría de la superficie).
Status HTTP
| Status | Significado |
|---|---|
| 400 | Body inválido, campo faltante o valor fuera de rango |
| 401 | Token ausente, inválido, expirado o revocado |
| 402 | Límite o capacidad del plan — SOURCE_LIMIT_REACHED (fuentes conectadas), PLAN_LIMIT_REACHED (documentos/consultas en Free) o FREQUENCY_NOT_ALLOWED (frecuencia de sync fuera del plan), ver abajo |
| 403 | Sin permiso — rol insuficiente o sin acceso al workspace |
| 404 | El recurso no existe o no pertenece a tu tenant |
| 409 | Conflicto — nombre duplicado, sync ya en curso, último admin, etc. |
| 422 | El archivo se procesó pero no produjo contenido indexable |
| 429 | Rate limit — reintenta con backoff |
| 500 | Error interno — reintenta; si persiste, contacta soporte |
| 503 | Dependencia no configurada en este ambiente (ej. storage u OCR deshabilitados) |
Catálogo de códigos
Genéricos (cualquier dominio)
| Código | HTTP | Cuándo ocurre |
|---|---|---|
INVALID_REQUEST_BODY | 400 | El body no es JSON válido |
MISSING_FIELDS | 400 | Falta un campo requerido |
VALIDATION_ERROR | 400 | Un campo tiene un valor inválido (ej. nombre demasiado largo) |
UNAUTHORIZED | 401 | Token ausente o inválido |
FORBIDDEN | 403 | Rol insuficiente para la operación |
NOT_FOUND | 404 | Recurso genérico no encontrado |
CONFLICT | 409 | Conflicto genérico |
TOO_MANY_REQUESTS | 429 | Rate limit excedido |
PLAN_LIMIT_REACHED | 402 | Límite de plan alcanzado (documentos y consultas en plan Free) |
SERVICE_UNAVAILABLE | 503 | Dependencia no configurada en este ambiente |
INTERNAL_ERROR | 500 | Error no esperado del servidor |
Auth
| Código | HTTP | Cuándo ocurre |
|---|---|---|
INVALID_CREDENTIALS | 401 | Email o password incorrectos en /auth/login |
INCORRECT_PASSWORD | 401 | Password actual incorrecto en /auth/change-password |
NO_PASSWORD_SET | 400 | La cuenta no tiene password establecido todavía |
INVALID_OTP | 401 | Código OTP inválido o expirado |
INVALID_OTP_PURPOSE | 400 | purpose no coincide con el flujo (signup/reset/login) |
OTP_SEND_FAILED | 500 | Falló el envío del email con el código |
WEAK_PASSWORD | 400 | El password no cumple la política mínima |
NO_WORKSPACE_ACCESS | 403 | El usuario no pertenece a ningún tenant |
TWO_FACTOR_REQUIRED | 403 | La cuenta tiene 2FA activo — usa login con password, no OTP directo |
EMAIL_ALREADY_REGISTERED | 409 | El email ya es admin de otro tenant (signup) |
SLUG_ALREADY_IN_USE | 409 | El slug de tenant ya existe (signup) |
INVALID_SLUG | 400 | El slug no cumple el formato (3–63 chars, alfanumérico + guiones) |
ACTIVATION_FAILED | 400 / 500 | Token de activación inválido, expirado, o fallo al activar |
Fuentes (ingestion)
| Código | HTTP | Cuándo ocurre |
|---|---|---|
SOURCE_NOT_FOUND | 404 | La fuente no existe o no pertenece a tu tenant |
SOURCE_LIMIT_REACHED | 402 | Tope de fuentes conectadas del plan alcanzado — solo cuentan conectores con sync; archivos subidos y fuentes en error sin chunks no |
CONFLICT | 409 | Ya existe una fuente web con esa URL normalizada — el message trae nombre e id de la existente |
INVALID_SOURCE_TYPE | 400 | source_type fuera del set permitido |
SYNC_IN_PROGRESS | 409 | Ya hay un sync en curso para esa fuente |
FREQUENCY_NOT_ALLOWED | 402 | La frecuencia de sincronización pedida no está incluida en tu plan. El message viene en español y nombra la frecuencia, tu plan actual y el mínimo que la habilita |
FILE_TOO_LARGE | 400 | Excede el máximo (50 MB genérico, 20 MB para imágenes) |
UNSUPPORTED_FILE_TYPE | 400 | MIME type no soportado |
FILE_PROCESSING_FAILED | 422 | El archivo se procesó pero no produjo chunks indexables |
Frecuencias de sincronización por plan
FREQUENCY_NOT_ALLOWED sale al programar un sync automático con una frecuencia que tu plan
no incluye. Estas son las combinaciones válidas:
| Frecuencia | Plan mínimo |
|---|---|
| Manual | Gratis |
| Cada 24 horas | Gratis |
| Cada 6 horas | Pro |
| Cada hora | Enterprise |
| Semanal | Enterprise |
El sync manual nunca está restringido: aunque tu plan no permita ninguna frecuencia automática, siempre puedes disparar una sincronización a mano.
Agentes y canales
| Código | HTTP | Cuándo ocurre |
|---|---|---|
AGENT_NOT_FOUND | 404 | El agente no existe o no pertenece a tu tenant |
INVALID_AGENT_CONFIG | 400 | Config inválida (status, origen, retención de transcripts, lead email) |
CHANNEL_CONNECT_FAILED | 400 | Falló la conexión del canal (ej. bot_token de Telegram rechazado) |
Webhooks salientes
| Código | HTTP | Cuándo ocurre |
|---|---|---|
WEBHOOKS_REQUIRE_PAID_PLAN | 403 | El plan del tenant no incluye webhooks salientes (Piloto en adelante) |
WEBHOOK_LIMIT_REACHED | 409 | Ya hay 5 endpoints configurados, el máximo por tenant |
WEBHOOK_NOT_FOUND | 404 | El endpoint no existe o no pertenece a tu tenant |
INVALID_WEBHOOK_URL | 400 | La URL no es HTTPS, está malformada, o apunta a una red interna o a un rango no permitido |
INVALID_WEBHOOK_EVENTS | 400 | Lista de eventos vacía, con repetidos, o con un evento fuera del catálogo |
Tenant / usuarios / perfil
| Código | HTTP | Cuándo ocurre |
|---|---|---|
USER_NOT_FOUND | 404 | El usuario no pertenece al tenant |
CANNOT_REMOVE_LAST_ADMIN | 409 | La operación dejaría al tenant sin ningún admin |
CANNOT_REMOVE_SELF | 409 | Un usuario no puede eliminar su propia membership |
INVALID_ROLE | 400 | El rol no es uno de admin|member|viewer|api_user|operator |
CANNOT_CHANGE_OWN_ROLE | 409 | Un usuario no puede cambiar su propio rol — el cambio lo tiene que hacer otro administrador |
MEMBERSHIP_NOT_FOUND | 404 | La cuenta no tiene membership en el tenant actual |
COMPANY_PROFILE_NOT_FOUND | 404 | El tenant no tiene perfil fiscal configurado |
Buenas prácticas
- Trata
codecomo el contrato estable;messagepuede cambiar sin aviso. - Ante
429, respeta un backoff exponencial — no reintentes inmediatamente. - Ante
402 SOURCE_LIMIT_REACHEDenPOST /ingest/sources: ya alcanzaste el límite de fuentes conectadas de tu plan. Solo cuentan los conectores con sincronización (web, Drive, Notion…) — los archivos subidos no ocupan este cupo, ni las fuentes en error que nunca indexaron nada. A diferencia de los excesos de documentos o consultas (que se registran y facturan sin bloquear — ver Límites de uso), este límite sí bloquea la creación del conector nuevo. - Ante
409 CONFLICTenPOST /ingest/sourcesconsource_type: web: tu espacio ya tiene una fuente con esa URL (la comparación normaliza mayúsculas, barra final, puerto default y fragmento). Elmessageincluye el nombre y el id de la fuente existente — re-sincronizala o borrala en vez de crear un duplicado.