API REST de Lightdash 1.107.0 — referencia reversada
Endpoints usados para automatizar el setup inicial (usuario, organización,
proyecto, token) y la creación de 3 dashboards con 17 gráficas por curl, sin
pasar por la UI. Ninguno de estos aparece documentado a este nivel de detalle
en docs.lightdash.com para esta versión — reversados leyendo directamente
/usr/app/packages/backend/dist/generated/routes.js (rutas + schemas
generados por tsoa) dentro del propio contenedor lightdash, y el bundle JS
del frontend (/assets/*.js) cuando el nombre del método no bastaba para
inferir el body exacto. Comando base usado para explorar:
docker exec lightdash grep -n '<patrón>' /usr/app/packages/backend/dist/generated/routes.jsTodos los ejemplos asumen sesión por cookie (-b cookies.txt -c cookies.txt)
salvo donde se indica un Personal Access Token. Ver
despliegue-cierre.md § Migración temporal de SECURE_COOKIES — con
SECURE_COOKIES=true estos ejemplos solo funcionan contra
https://dash.frento.com.mx, no contra http://localhost:8090.
Setup inicial (instancia sin organización todavía)
1. Registrar el primer usuario
POST /api/v1/user
{"firstName": "...", "lastName": "...", "email": "...", "password": "..."}
Devuelve organizationUuid: null — el usuario existe pero no pertenece a
ninguna organización todavía. GET /api/v1/health confirma el estado con
"requiresOrgRegistration": true.
2. Crear la organización — método PUT, no POST
PUT /api/v1/org
{"name": "Trivasa"}
Gotcha real: POST /api/v1/org devuelve 404 Not Found (la ruta
simplemente no existe con ese verbo) — solo PUT está registrado
(OperationId: CreateOrganization, decorador @Put() sobre la raíz del
controller). El intento obvio (POST, por paralelismo con el registro de
usuario) no funciona y el error 404 no da ninguna pista de que el verbo
correcto es PUT.
Un endpoint que parecía candidato natural,
PATCH /api/v1/user/me/complete (documentado en discusiones antiguas de
GitHub del proyecto con el body
{"organizationName": ..., "jobTitle": ..., ...}), no existe en esta
versión — devuelve 403 Forbidden: "User is not part of an organization",
señal de que esa ruta espera que la organización ya exista (probablemente
vestigio de un flujo de onboarding distinto en versiones anteriores de
Lightdash). No usar como referencia para 1.107.0.
3. Login (sesión por cookie)
POST /api/v1/login
{"email": "...", "password": "..."}
4. Personal Access Token (para el CLI)
POST /api/v1/user/me/personal-access-tokens
{"description": "cli-deploy", "expiresAt": null, "autoGenerated": false}
Respuesta incluye token en texto plano una sola vez (ldpat_...) — no se
puede recuperar después, solo revocar/regenerar desde la UI (avatar →
Personal access tokens).
Proyecto (warehouse + dbt)
POST /api/v1/org/projects
{
"name": "Trivasa BI",
"type": "DEFAULT",
"dbtConnection": {"type": "none"},
"dbtVersion": "latest",
"warehouseConnection": {
"type": "postgres",
"host": "postgres-dw",
"port": 5432,
"user": "trivasa",
"password": "...",
"dbname": "trivasa_dw",
"schema": "analytics",
"sslmode": "disable"
}
}
dbtConnection.type: "none" es el tipo correcto para el flujo CLI (el manifest
se sube ya compilado desde fuera, el servidor nunca ejecuta dbt). Valores
válidos del enum DbtProjectType: dbt (local, servidor ejecuta dbt),
dbt_cloud_ide, github, gitlab, bitbucket, azure_devops, none,
manifest.
sslmode: "disable" es obligatorio aquí porque postgres-dw no tiene SSL
habilitado — sin este campo, cualquier compilación posterior falla con
The server does not support SSL connections (mensaje textual de Postgres al
rechazar la negociación SSL). No basta con ponerlo aquí — el CLI local
también negocia SSL por su cuenta vía ~/.dbt/profiles.yml, que necesita su
propio sslmode: disable en el output del profile (ver dbt-project.md o la
página de wiki para el profiles.yml completo). Los dos son conexiones
independientes al mismo Postgres; arreglar una sin la otra deja el error a
mitad de camino.
No existe endpoint de actualización para un proyecto ya creado
Se buscó exhaustivamente (grep de todo app.patch/app.put sobre
/api/v1/projects/:projectUuid sin subpath, y sobre /api/v1/org/projects)
y no hay ninguna ruta que permita corregir warehouseConnection o
dbtConnection de un proyecto existente en 1.107.0. Solo existe
GET /api/v1/projects/:projectUuid. Un archivo del propio paquete @lightdash/cli
(handlers/setWarehouse.js) apunta a PATCH /api/v1/projects/{projectUuid}
como si existiera — no está registrado en routes.js de este build del
servidor, ese comando del CLI está roto o depende de una versión de servidor
distinta a la desplegada aquí. Tampoco se encontró endpoint de borrado de
proyecto (DELETE) en ningún controller.
Consecuencia práctica: el primer proyecto creado sin sslmode quedó
huérfano, sin explores, imposible de arreglar ni borrar por API — la única
salida fue crear un segundo proyecto (POST /api/v1/org/projects de nuevo)
con la config correcta desde cero. El proyecto viejo sigue existiendo en la
organización, visible en la UI, 0 explores.
Space (contenedor requerido para dashboards)
POST /api/v1/projects/{projectUuid}/spaces
{"name": "Trivasa"}
Dashboard + charts en una sola llamada
POST /api/v1/projects/{projectUuid}/dashboards/with-charts
{
"name": "Compras",
"description": "...",
"spaceUuid": "...",
"charts": [ {...}, {...}, ... ]
}
Cada objeto de charts[]:
{
"name": "Total comprado",
"tableName": "fct_compras",
"metricQuery": {
"exploreName": "fct_compras",
"dimensions": [],
"metrics": ["fct_compras_total_compras"],
"filters": {},
"sorts": [],
"limit": 1,
"tableCalculations": []
},
"chartConfig": {
"type": "big_number",
"config": {"label": "Total comprado", "selectedField": "fct_compras_total_compras"}
},
"tableConfig": {"columnOrder": ["fct_compras_total_compras"]}
}No incluir dashboardUuid ni spaceUuid dentro de cada chart — el schema
CreateSavedChart es una unión (CreateChartInSpace | CreateChartInDashboard)
donde el segundo variant pide dashboardUuid requerido, pero el endpoint
bulk asigna los charts al dashboard recién creado automáticamente a partir del
spaceUuid del body externo; incluir dashboardUuid: null a mano no hace
falta y complica el payload sin necesidad. El layout de grid (posición
x/y/w/h de cada tile) también se calcula solo — no hay forma de especificarlo
en esta llamada, ni se intentó controlarlo.
Field ID = ${nombre_del_modelo_dbt}_${nombre_de_dimensión_o_métrica} — ej.
fct_compras_total_compras, fct_movimientos_fecha_month. Se obtiene sin
adivinar consultando GET /api/v1/projects/{projectUuid}/explores/{tableName}
y leyendo las claves de results.tables[tableName].dimensions /
.metrics.
chartConfig por tipo usado
type | config mínimo funcional |
|---|---|
big_number | {"label": "...", "selectedField": "<fieldId>"} |
cartesian | {"layout": {"xField": "<fieldId>", "yField": ["<fieldId>"], "flipAxes": true|false}, "eChartsConfig": {"series": [{"encode": {"xRef": {"field": "<fieldId>"}, "yRef": {"field": "<fieldId>"}}, "type": "bar"|"line"}]}} |
pie | {"groupFieldIds": ["<fieldId>"], "metricId": "<fieldId>", "showLegend": true, "showPercentage": true} |
table | {} (todos los campos son opcionales; tableConfig.columnOrder en el nivel superior del chart controla qué columnas muestra) |
Todos los tipos de ChartConfig en el schema (BigNumberConfig,
CartesianChartConfig, CustomVisConfig, PieChartConfig,
FunnelChartConfig, TableChartConfig, TreemapChartConfig,
GaugeChartConfig, DataAppVizChartConfig, MapChartConfig,
SankeyChartConfig) usan la envoltura {"type": "<tipo>", "config": {...}} —
solo se probaron los cuatro de la tabla de arriba.
sorts usa {"fieldId": "...", "descending": true|false} — útil para “top N”
combinado con limit (ej. top 10 proveedores por gasto: sorts descendente
por la métrica + limit: 10).
Explorar un modelo (para obtener sus field IDs)
GET /api/v1/projects/{projectUuid}/explores/{tableName}
Ejecutar la query de un chart guardado (verificar datos sin abrir la UI)
POST /api/v1/saved/{chartUuid}/results
{}
Útil para confirmar que un chart devuelve datos reales (o para reproducir un
error de un chart roto) sin necesidad de cargar la UI completa — así se
verificó cada bug de despliegue-cierre.md antes y después del fix.
Favoritos (sustituto FOSS del “pin to homepage”)
PATCH /api/v1/projects/{projectUuid}/favorites
{"contentUuid": "<uuid>", "contentType": "dashboard"}
contentType acepta chart | dashboard | space | data_app (enum
ContentType). Alterna el estado (toggle) — la misma llamada quita el
favorito si ya estaba puesto.
La pantalla “Welcome” con pinned items es Enterprise, no FOSS
GET /api/v1/projects/{projectUuid}/homepage devuelve:
{"status":"error","error":{"statusCode":422,"name":"MissingConfigError",
"message":"Unable to initialize service 'projectHomepageService' - no factory or provider."}}El controller vive en packages/backend/dist/ee/controllers/projectHomepageController.js
(carpeta ee/ = Enterprise Edition) con funciones de un “homepage builder”
completo (getResolved, getForBuilder, viewAs por usuario/grupo/rol) — no
es el mecanismo simple de “pin” de versiones antiguas de Lightdash. Sin
license key (hasLicenseKey: false en /api/v1/health), el servicio nunca se
inicializa. No hay forma de poblar esa pantalla en un deployment sin licencia,
por API o por UI — Favoritos es el mecanismo real disponible.
Nota aparte: existe un PinningController
(/api/v1/projects/{projectUuid}/pinned-lists/{pinnedListUuid}/items/order,
GET/PATCH) y un método de servicio togglePinning en DashboardService,
pero ningún controller expone togglePinning por REST en este build — parece
código huérfano de un mecanismo de pin anterior al homepage EE actual, no
usable desde afuera.
Véase también
despliegue-cierre.md— narrativa completa del despliegue (RAM/disco, los dos bugs post-deploy) donde se usó todo lo de este documento.- Lightdash — página de wiki con el resumen en prosa.