API HTTP
La interfaz web y el CLI usan esta API, y no hay nada que solo pueda hacerse desde el navegador. Esta página es la referencia: autenticación, convenciones y la lista de rutas por área. El comportamiento de cada operación se explica en su guía.
Autenticación
| Credencial | Quién la usa | Cómo se envía |
|---|
| Cookie de sesión | El navegador | Cookie: orchestrator_session=… |
| Token de API | Programas, el CLI y el MCP | Authorization: Bearer fdi_… |
Un token tiene los mismos derechos que la sesión de su dueño; lo que cambia es cómo se retira: revocar un token no cierra la sesión del navegador, ni al revés. Se emite con fundaia login o desde Línea de comandos en la interfaz.
Toda ruta exige credencial salvo unas pocas públicas: la documentación (/llms.txt, /docs/…, /agent.md), GET /api/version, GET /api/infra/plans, las novedades (GET /api/infra/changelog y su canal Atom), /robots.txt, /sitemap.xml y /site.webmanifest, los webhooks de GitHub y Stripe (que se verifican por firma) y el inicio del login del CLI.
Convenciones
- Dos identificadores de servicio.
serviceEnvironmentId es un servicio en un entorno y es el que usan casi todas las rutas. serviceId es el servicio a secas y solo lo usan la posición en el lienzo, el nombre y el borrado. Confundirlos da 404. - **
PATCH es parcial**: una clave ausente se deja como estaba. - Un despliegue es asíncrono.
POST …/deploy responde 202; el progreso está en GET /api/infra/deployments/:id, en el campo pipeline, y termina cuando finished es true. - Crear un servicio lo despliega;
"deployNow": false lo evita. - Un recurso de otro espacio responde 404, igual que uno que no existe.
- El entorno va en la consulta. Las lecturas de proyecto (servicios, lienzo, salud, manifiesto, auditoría de variables, búsqueda en registros) aceptan
?environment= con el id, el slug o el nombre de uno de sus entornos; sin él, el entorno por defecto. Uno que no es del proyecto responde
- Un
serviceEnvironmentId ya dice en qué entorno está.
Errores
{ "error": "conflict", "message": "This project already has a service called \"api\"" }
message es para una persona y puede cambiar de redacción. error es el código sobre el que programar: validation_failed, not_found, conflict, forbidden, unauthorized, rate_limited, external_service_failed y deploy_frozen (423: el entorno está congelado; force: true en el cuerpo o ?force=true lo hace igualmente).
El pipeline
{
"pipeline": {
"currentStageId": "build",
"finished": false,
"succeeded": false,
"stages": [
{ "id": "queue", "label": "En cola", "state": "done", "startedAt": "…", "finishedAt": "…" },
{ "id": "build", "label": "Construyendo la imagen", "state": "running" }
]
}
}
Etapas: queue, checkout, build, start, health, live. Estados: pending, running, skipped, done, failed, cancelled.
Versión
GET /api/version es público y dice qué está corriendo, para comprobar la compatibilidad antes de autenticarse:
{ "platform": "0.1.0", "revision": null, "modules": { "infrastructure": "0.1.0" } }
Rutas
Todas bajo /api/infra. :service es un serviceEnvironmentId salvo donde se indica.
Proyectos y espacios
| |
|---|
GET /projects | Todos, con sus servicios |
POST /projects | { name, description?, workspaceId? } |
GET /projects/:project | El proyecto y sus entornos, con serviceCount; el por defecto primero |
PATCH /projects/:project | { name?, description? } |
PATCH /projects/:project/workspace | { workspaceId }: moverlo a otro espacio |
GET /projects/:project/image | la foto, con su Content-Type; ?v= la deja en caché para siempre. image en la lista y en el detalle trae { url, source, origin } o null |
PUT /projects/:project/image | los bytes de la imagen tal cual (no JSON), hasta 512 KB |
DELETE /projects/:project/image | quitarla; nada la vuelve a poner sola |
GET /projects/:project/notes | { notes: { body, updatedAt, updatedBy } }; body vacío si nadie las escribió |
PUT /projects/:project/notes | { body }: el Markdown entero (hasta 20.000 caracteres; vacío las borra) |
POST /projects/:project/image/detect | buscarla en los repositorios: { outcome: found\|nothing\|no-repository\|no-github, image, searched? } |
DELETE /projects/:project?confirm=<nombre> | Lo elimina entero; sin confirm responde 400 |
GET /projects/:project/activity | El historial |
GET /projects/:project/canvas · PATCH · POST …/canvas/arrange | Posiciones, flechas; { positions: [{ serviceId, x, y }] }; { layout: "layered" \| "grid" }. Las posiciones son del servicio en todos sus entornos |
GET /projects/:project/environments | Sus entornos |
POST /projects/:project/environments | { name, copyFrom?, deploy?, ttl? }: copyFrom es el entorno a copiar (el por defecto si falta, null para uno vacío); ttl (48h, 7d) lo hace caducar |
PATCH /projects/:project/environments/:environment | { name?, ttl? }; el slug no cambia; ttl: null (o "never") quita la caducidad |
DELETE /projects/:project/environments/:environment?confirm=<nombre> | Lo elimina entero; nunca el por defecto |
POST /projects/:project/environments/:environment/services | { from: serviceEnvironmentId, deploy? }: copia un servicio a ese entorno; 409 si ya está |
GET /projects/:project/environments/:environment/promote?to=<entorno>&services=a,b | El plan de promover ese entorno a otro: qué cambia, de qué imagen a cuál |
POST /projects/:project/environments/:environment/promote | { to, services? }: despliega en to las imágenes que corren aquí, sin construir; 202 |
GET /projects/:project/environments/:environment/copy-data?to=<entorno> | Qué volúmenes se copiarían a to |
POST /projects/:project/environments/:environment/copy-data | { to, services?, confirm }: sustituye los datos de to, con copia de seguridad antes; confirm es su nombre (el exacto, si es el por defecto) |
GET /projects/:project/environments/compare?from=<entorno>&to=<entorno> | Las variables que cambian entre dos entornos; los secretos, solo igual o distinto |
GET /workspaces · POST /workspaces · PATCH /workspaces/:id | Espacios |
GET·POST /workspaces/:id/invitations · DELETE …/:invitation | Invitaciones |
GET /workspaces/:id/members · PATCH·DELETE …/:user | Miembros y roles |
GET·PUT /workspaces/:id/notifications | Avisos por correo de quien llama |
Servicios
| |
|---|
GET /projects/:project/services | Nodos y aristas del lienzo, del entorno de ?environment= |
POST /projects/:project/services | Crea uno; ver abajo |
GET /services/:service · PATCH | Detalle y ajustes; autoscale: { min, max, targetCpuPercent?, targetRequestsPerMinute?, cooldownSeconds? } o null reparte las réplicas solo (Autoescalado) |
PATCH /services/:serviceId/name | { name } |
PATCH /services/:serviceId/layout | { x, y } |
DELETE /services/:serviceId | Lo borra; conserva los volúmenes |
POST /projects/:project/detected-apps · POST /services/:service/detected-apps | Las aplicaciones de un monorepo, sin clonar |
GET·PUT /services/:service/recipe | El Containerfile; { containerfile } lo congela |
GET·POST·PUT /services/:service/config | El fichero del balanceador o del gateway |
GET·PUT /services/:service/agent | Token, modelo y esfuerzo de un componente Claude Code |
GET /services/:service/uploads · POST | Código subido (.zip o .tar.gz) |
PUT /services/:service/public-access | { publicAccess } |
GET /templates | El catálogo |
GET /catalogue/templates · GET /catalogue/templates/:key | El catálogo público, sin sesión: pilas y componentes con lo que crean (imágenes, volúmenes, nombres de variables, nunca valores) y estimate si la instancia cobra |
GET /deploy-links?plantilla=\|repo=&rama=&carpeta=&nombre=&tema= | Sin sesión: lo que crea un enlace «Desplegar en Fundaia» y sus fragmentos markdown y html |
GET /deploy-links/repository?repo=&rama= | access: connected (en tu conexión), public o unreachable, y la rama que se desplegaría |
POST /deploy-links/projects | { template \| repository, branch?, rootDirectory?, name?, workspaceId? }: crea el proyecto y lo despliega; 201 |
GET /deploy-button.svg?tema=oscuro\|claro | La imagen del botón, sin sesión y en caché una semana |
GET /repositories?q= | Repositorios desplegables, con sharedBy |
Crear un servicio:
{
"name": "api",
"sourceKind": "github",
"repoFullName": "owner/api",
"branch": "main",
"rootDirectory": "apps/api",
"connectTo": ["sve_..."]
}
sourceKind es github, image (con image), template (con templateKey) o upload. environmentId (id, slug o nombre) dice en qué entorno se crea; sin él, en el por defecto.
Crear un entorno responde 201 con lo que pasó con cada servicio:
{
"environment": { "id": "env_...", "name": "staging", "slug": "staging", "isDefault": false, "serviceCount": 2 },
"copiedFrom": { "id": "env_...", "name": "production", "slug": "production", "isDefault": true },
"services": [
{ "name": "Postgres", "status": "deploying", "reason": null, "volumes": ["/var/lib/postgresql/data"], "host": null, "notes": [] },
{ "name": "api", "status": "deploying", "reason": null, "volumes": [], "host": "tienda-api--staging.fundaia.com", "notes": [] }
],
"variables": { "copied": ["REGION"], "skipped": [] }
}
status es deploying, copied (está, sin arrancar; reason dice por qué) o skipped (no se copió; reason dice por qué).
Despliegues
| |
|---|
POST /services/:service/deploy | 202 con el despliegue |
POST /services/:service/restart | Reutiliza la imagen viva |
POST /services/:service/stop | Lo duerme |
GET /services/:service/deployments | Historial, liveId y aprobación pendiente; ?commit= filtra |
GET /deployments?scope=active\|recent\|failed\|all&limit= | Los de todos tus proyectos: en curso y últimas 24 horas |
GET /deployments/:deployment | El despliegue y su pipeline |
POST /deployments/:deployment/redeploy · …/rollback · …/cancel | cancel responde 409 si ya terminó |
GET /services/:service/commits/:sha | Los cambios de un commit |
GET /projects/:project/approvals · GET /services/:service/approvals | Commits esperando |
POST /approvals/:id/approve · …/reject | |
GET·POST /services/:service/repository-access | Acceso a un repositorio de otra persona |
Variables, conexiones y dominios
| |
|---|
GET /services/:service/variables | Nunca incluye el valor de un secreto |
PUT /services/:service/variables | { key, value, secret?, sealed? } |
POST /services/:service/variables/import | Un .env entero |
DELETE /variables/:id · POST …/reveal · POST …/seal | reveal queda registrado |
GET /services/:service/variables/resolved | Las variables resueltas, secretos incluidos, para fundaia run; sin las selladas ni las de solo escritura (en withheld, con el motivo); queda registrado |
GET·POST /services/:service/connections · DELETE …/:providerId | GET dice a qué podría conectarse; POST { providerId } |
GET·POST /services/:service/domains | POST sin host genera uno |
DELETE /domains/:id · POST …/verify | |
Volúmenes, copias y datos
| |
|---|
GET·POST /services/:service/volumes | POST { mountPath } |
PATCH /volumes/:id · DELETE | { sizeLimitMb }; DELETE lo desprende, no lo borra |
GET·POST /services/:service/backups · PUT …/backups/schedule | Copias y programación |
POST /backups/:id/restore · PUT …/lock · DELETE | |
PUT /services/:service/encryption | Cifrado de un bucket |
GET /services/:service/data · GET …/data/rows | Tablas (o colecciones) de un PostgreSQL, MySQL, SQLite, MongoDB o ClickHouse; status.engine dice cuál |
POST /services/:service/data/query · POST …/data/execute | { sql }, que en MongoDB es un comando JSON. Solo lectura; una sentencia, con preview (rehearsal: "unavailable" si el motor no puede deshacerla) |
Registros, métricas e incidencias
| |
|---|
GET /deployments/:deployment/log | Una página desde ?after=, o las últimas líneas con ?tail= (hasta 5000); ?stage=, ?stream= y ?traceId= filtran |
GET /deployments/:deployment/log/stream | Historial y luego en vivo, por SSE |
GET /services/:service/metrics · GET …/usage?minutes= | Series y muestras |
GET /services/:service/consumption · GET …/activity | Consumo y actividad de un agente |
GET /services/:service/incidents · PATCH /incidents/:id · POST …/fix | |
GET·PUT /services/:service/autofix · GET /autofix/:run | |
POST /deployments/:deployment/fix | Claude arregla un despliegue fallido |
GET·POST /services/:service/security-scans · GET /security-scans/:id | Vulnerabilidades; { fix: true } abre pull request |
POST /issues · GET /services/:service/issues | { kind, id }: incidencia, despliegue o escaneo |
GET /workspaces/:workspace/costs · GET /projects/:project/costs · GET /services/:service/costs | La previsión: forecast (spentMicros, projectedMicros, dailyRateMicros, beyondPlanMicros, projectedBillMicros, confidence…), window, daily y projection por día, y por proyecto y servicio |
GET·PUT·DELETE /projects/:project/budget | El presupuesto: { euros: 25 \| "25,50" \| "off", action: notify\|sleep }; responde budget, status, held y sleepPlan |
GET /projects/:project/savings · GET /workspaces/:workspace/savings | Las recomendaciones de ahorro, con monthlySavingMicros y actions |
POST /projects/:project/savings/:saving/apply · POST·DELETE …/dismiss | { action, confirm? } aplica una; dismiss la oculta y DELETE la recupera |
GET /workspaces/:workspace/digest/preview · POST …/digest/send-to-me | El resumen semanal: { facts, subject, html, text }, o mandártelo ya |
GET·PUT /projects/:project/status-page | La página de estado: { enabled, title, services, timezone, rotateToken }; la lectura trae timeline y suggestion |
GET·POST /projects/:project/status-page/incidents · PATCH·DELETE …/incidents/:incident | Incidentes: { title, impact, components, state, message }; :incident es su id o su título |
POST …/incidents/:incident/updates · POST …/resolve · PATCH·DELETE …/updates/:update | Novedades { state, message, impact }, resolver { message }, corregir o borrar una |
GET·POST /projects/:project/status-page/maintenance · DELETE …/maintenance/:id | Mantenimientos { title, startsAt, endsAt, components, message }; borrar cancela o termina ya |
GET /status/:token | Lo que ve un visitante, sin sesión, con level, incidents, pastIncidents y maintenance; 404 si no existe o está retirada |
GET /status/:token/feed.atom · …/badge.svg · …/image | Feed Atom, insignia SVG y foto del proyecto, sin sesión |
GET /platform-status · …/feed.atom · …/badge.svg | El estado de Fundaia, sin sesión |
GET /platform-status/manage · …/incidents · …/maintenance | Lo mismo que en un proyecto, solo el administrador |
GET·POST /projects/:project/monitors · GET·PATCH·DELETE …/monitors/:monitor | Monitores HTTP: { serviceEnvironmentId, via, path } o { url }, más intervalSeconds, timeoutSeconds, expectedStatus, keyword, failuresToAlert, wake, enabled; el detalle trae la disponibilidad y sparkline. De cron: { kind: "heartbeat", periodSeconds \| cronExpression + cronTimezone \| scheduledTaskId, graceSeconds }; crearlo devuelve ping con la URL una sola vez |
POST /projects/:project/monitors/:monitor/ping-address | Una URL de ping nueva para un monitor de cron; la anterior deja de valer |
GET·POST·HEAD /ping/:token · …/:token/start · …/:token/fail | El ping de un monitor de cron, sin sesión; un cuerpo de hasta 1 KB es el mensaje; 404 si el token no existe, 429 si llega más de 12 veces por minuto |
GET /services/:service/replicas | Los contenedores de un servicio y autoscale: la política, cuántas quiere, cuántas tiene y sus últimas decisiones |
GET·POST /projects/:project/alerts · GET·PATCH·DELETE …/alerts/:alert | Alertas: { serviceEnvironmentId, kind: log\|error-rate\|latency, pattern, patternKind, threshold, windowMinutes, minimumRequests, cooldownMinutes, enabled } |
GET /projects/:project/services/:service/analytics?range=&environment= | Analítica web de un servicio: totals, previous, change, today, series, top (paths, sources, campaigns, countries, browsers, systems, devices) y statuses; range es 24h, 7d, 30d, 90d o 12m |
GET /projects/:project/analytics?range=&environment= | La misma cuenta, por servicio y sumada para el proyecto |
GET·POST /projects/:project/log-drains · GET·PATCH·DELETE …/log-drains/:drain · POST …/test | Envío de logs: { kind: http\|betterstack\|axiom\|datadog\|loki\|otlp, name, url, secret, host, dataset, site, tenant, username, headers, format, services, environments, includeBuild, enabled }; los secretos nunca vuelven, solo secretHint |
Diagnóstico
| |
|---|
GET /attention | Lo que está mal en todo lo que ves, lo peor primero; ?environment=staging mira ese entorno en cada proyecto que lo tenga |
GET /projects/:project/health | Un veredicto por proyecto y cada servicio diagnosticado |
GET /services/:service/diagnosis · GET …/dependencies | Hallazgos con la siguiente llamada; quién depende de quién |
GET /projects/:project/variables/audit | Referencias ${{ }} que no resolverán |
GET /projects/:project/logs/search?q= | service, level, stream, hours, limit |
GET /projects/:project/manifest | El proyecto como JSON; ?download=1 lo descarga |
GET /deployments/:a/compare/:b | Mismo código, variables y configuración |
POST /services/:service/probe · POST …/wake | { path } contra el contenedor; despertar a mano |
GET /projects/:project/workflows/stats | Tasa de éxito, duración mediana, inestables |
GET /stream | Cambios de estado de todo lo que ves, por SSE |
GET /capacity | Cuánta máquina queda |
El canal de un despliegue lleva sus líneas de registro; el global (/stream) solo cambios de estado, para no necesitar una conexión por servicio.
Workflows
| |
|---|
GET /projects/:project/workflows · POST …/workflows/refresh | Los ficheros, por rama |
GET·POST /projects/:project/workflow-runs | ?path=, ?commit=, ?parent=; POST lanza uno |
GET /workflow-runs/:id · POST …/cancel · POST …/rerun | |
GET /workflow-jobs/:id/logs?after= · GET …/artifact | |
Foro
| |
|---|
GET /forum/categories | Las categorías con cuántas conversaciones tiene cada una (público) |
GET /forum/threads | ?category=&sort=recent\|top\|unanswered&q=&page=; { threads, total, hasMore, viewer } (público) |
POST /forum/threads | { category, title, body } |
GET /forum/threads/:id | La conversación, sus respuestas y can (qué puede hacer quien lee) (público) |
PATCH·DELETE /forum/threads/:id | { title?, body?, category? }; borrar la oculta |
POST /forum/threads/:id/replies | { body } |
PUT /forum/threads/:id/accepted | { replyId }; null la quita |
PATCH /forum/threads/:id/moderation | { pinned?, locked?, hidden? } (solo quien administra) |
PATCH·DELETE /forum/replies/:id · PATCH …/moderation | { body }; { hidden } |
POST /forum/posts/:id/vote | Sin cuerpo alterna el voto; { voted } lo fija |
Búsqueda y asistente
| |
|---|
GET /search?q=&kind=&limit= | En todos los proyectos que ves, agrupado por tipo (guía) |
GET /assistant | Si está disponible, el modelo y su precio por millón de tokens |
GET·POST /assistant/conversations · GET·PATCH·DELETE …/:id | Tus conversaciones (guía) |
POST /assistant/conversations/:id/messages | { text }: la respuesta llega como flujo SSE |
POST /assistant/conversations/:id/proposals/:proposal/confirm · …/dismiss | Decidir el cambio propuesto; flujo SSE |
Novedades
| |
|---|
GET /changelog | Las versiones de la plataforma, la más reciente primero: version, date, anchor, headline y sus secciones con entradas en Markdown. ?limit= (hasta 200) o ?version= (con o sin v). Público, cacheable cinco minutos |
GET /changelog/feed.atom | Las últimas 30 versiones en Atom, para un lector de feeds. Público |
Facturación
| |
|---|
GET /plans | Los planes (público) |
GET /workspaces/:id/billing · PUT …/billing/limit | Estado y límite de gasto |
POST /workspaces/:id/billing/checkout · …/billing/portal | Suscribirse y gestionar el pago en Stripe |
GET /workspaces/:id/credits · POST …/credits/checkout | Saldo del agente y recargas |