API HTTP
Endpoints, autenticación y códigos de error.
La API del panel es REST sobre JSON. Base: https://api.growthlab.ecomlabs.dev.
Autenticación
El panel usa una cookie de sesión (gl_session, HttpOnly, Secure, SameSite=Lax) más un token CSRF en la cabecera X-GrowthLab-CSRF para toda petición que muta estado.
El token CSRF está firmado y ligado al id de sesión, así que un atacante que pudiera escribir cookies en un subdominio del dominio padre no podría producir uno válido para la sesión de la víctima.
/v1/auth/me devuelve un token fresco y refresca la cookie en la misma respuesta. Devolver un token sin actualizar la cookie rompería la siguiente mutación.
Códigos de error
Toda respuesta de error tiene la misma forma:
{
"error": {
"code": "not_found",
"message": "Resource not found",
"requestId": "0d0a2b1e-..."
}
}
| Código | HTTP | Significado |
|---|---|---|
unauthenticated |
401 | Sin sesión válida |
csrf_failed |
403 | Token CSRF ausente o inválido |
forbidden |
403 | El rol no tiene el permiso |
not_found |
404 | No existe o pertenece a otra organización |
validation_failed |
400 | Payload inválido; details lleva rutas, nunca valores |
weak_password |
400 | Contraseña rechazada por la política |
email_taken |
409 | Ya existe una cuenta |
key_taken |
409 | Clave de experimento duplicada |
invalid_transition |
409 | Cambio de estado no permitido |
plan_limit_reached |
402 | Cuota del plan agotada |
account_locked |
423 | Bloqueo temporal por intentos fallidos |
too_many_attempts |
429 | Rate limit |
internal_error |
500 | Error no controlado; usa requestId para soporte |
404 y no 403 entre organizaciones
Un recurso de otra organización devuelve 404, no 403. Un 403 confirmaría que el recurso existe, lo que filtra la existencia de datos de otro cliente a cualquiera que adivine ids.
Los errores nunca llevan stack trace
Un 500 devuelve un requestId y nada más. El detalle queda solo en el log del servidor. Un stack trace expone rutas de archivos, versiones de dependencias y a veces fragmentos de consulta.
validation_failed no devuelve los valores
details lleva la ruta del campo y el mensaje, nunca el valor enviado. Ese payload puede contener la contraseña que el usuario acaba de intentar registrar.
Endpoints
Autenticación
| Método | Ruta | Descripción |
|---|---|---|
POST |
/v1/auth/register |
Crea usuario, organización y proyecto |
POST |
/v1/auth/login |
Inicia sesión |
POST |
/v1/auth/logout |
Revoca la sesión en el servidor |
GET |
/v1/auth/me |
Usuario, organizaciones, features y token CSRF |
POST |
/v1/auth/forgot-password |
Solicita restablecimiento |
POST |
/v1/auth/reset-password |
Aplica restablecimiento |
register no crea un sitio: un sitio sin dominio registrado sería rechazado por el colector en cada evento y consumiría la única plaza del plan gratuito. La respuesta incluye nextStep: "create_site".
login responde igual ante contraseña incorrecta y ante correo inexistente, y ejecuta una verificación Argon2 completa en ambos casos para que el tiempo de respuesta no revele qué correos están registrados.
Sitios
| Método | Ruta |
|---|---|
GET |
/v1/organizations/:organizationId/sites |
POST |
/v1/sites |
GET |
/v1/sites/:siteId/snippet |
POST |
/v1/sites/:siteId/verify-installation |
POST |
/v1/sites/:siteId/kill-switch |
primaryDomain debe ser un hostname desnudo (tienda.com), no una URL. Se aceptan hosts de una sola etiqueta como localhost para integración local.
Experimentos
| Método | Ruta |
|---|---|
GET |
/v1/sites/:siteId/experiments |
POST |
/v1/experiments |
POST |
/v1/experiments/:experimentId/status |
Analítica
| Método | Ruta |
|---|---|
GET |
/v1/sites/:siteId/traffic?from=&to= |
GET |
/v1/sites/:siteId/friction?from=&to= |
GET |
/v1/experiments/:experimentId/results?from=&to= |
results devuelve el análisis completo: SRM, posteriores de conversión, bootstrap de revenue, decisión y bloqueadores. Nunca devuelve un ganador sin los bloqueadores que lo justifican.
Configuración del SDK (pública)
| Método | Ruta |
|---|---|
GET |
/v1/sdk/config/:siteId |
Sin autenticación, cacheable, Access-Control-Allow-Origin: *. Contiene lo que el navegador necesita y nada más: ni datos de organización, ni secretos. Va firmada con HMAC.
Rate limiting
| Superficie | Límite por defecto |
|---|---|
| API autenticada | 300 req/min por IP |
| Login por correo | 10 intentos / 5 min |
| Restablecimiento por correo | 5 / 15 min |
| Colector de eventos | 6.000 req/min por IP |
| Colector de sesiones | 300 req/min por IP |
El limitador usa Redis compartido, así que el límite es global y no se multiplica al escalar réplicas. Si Redis falla, permite el paso: el rate limiting es un control de abuso, no de autorización, y hacerlo fallar cerrado convertiría una incidencia de Redis en una caída total.
CORS
La API mantiene una lista blanca estricta de orígenes porque el panel envía credenciales. Los colectores aceptan cualquier origen —los necesitan— pero no aceptan cookies, y verifican que el origen sea un dominio registrado del sitio.