Fundaia

Entrar Empezar

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

CredencialQuién la usaCómo se envía
Cookie de sesiónEl navegadorCookie: orchestrator_session=…
Token de APIProgramas, el CLI y el MCPAuthorization: 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
  1. 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 /projectsTodos, con sus servicios
POST /projects{ name, description?, workspaceId? }
GET /projects/:projectEl 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/imagela 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/imagelos bytes de la imagen tal cual (no JSON), hasta 512 KB
DELETE /projects/:project/imagequitarla; 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/detectbuscarla 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/activityEl historial
GET /projects/:project/canvas · PATCH · POST …/canvas/arrangePosiciones, flechas; { positions: [{ serviceId, x, y }] }; { layout: "layered" \| "grid" }. Las posiciones son del servicio en todos sus entornos
GET /projects/:project/environmentsSus 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,bEl 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/:idEspacios
GET·POST /workspaces/:id/invitations · DELETE …/:invitationInvitaciones
GET /workspaces/:id/members · PATCH·DELETE …/:userMiembros y roles
GET·PUT /workspaces/:id/notificationsAvisos por correo de quien llama

Servicios

GET /projects/:project/servicesNodos y aristas del lienzo, del entorno de ?environment=
POST /projects/:project/servicesCrea uno; ver abajo
GET /services/:service · PATCHDetalle 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/:serviceIdLo borra; conserva los volúmenes
POST /projects/:project/detected-apps · POST /services/:service/detected-appsLas aplicaciones de un monorepo, sin clonar
GET·PUT /services/:service/recipeEl Containerfile; { containerfile } lo congela
GET·POST·PUT /services/:service/configEl fichero del balanceador o del gateway
GET·PUT /services/:service/agentToken, modelo y esfuerzo de un componente Claude Code
GET /services/:service/uploads · POSTCódigo subido (.zip o .tar.gz)
PUT /services/:service/public-access{ publicAccess }
GET /templatesEl catálogo
GET /catalogue/templates · GET /catalogue/templates/:keyEl 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\|claroLa 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/deploy202 con el despliegue
POST /services/:service/restartReutiliza la imagen viva
POST /services/:service/stopLo duerme
GET /services/:service/deploymentsHistorial, 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/:deploymentEl despliegue y su pipeline
POST /deployments/:deployment/redeploy · …/rollback · …/cancelcancel responde 409 si ya terminó
GET /services/:service/commits/:shaLos cambios de un commit
GET /projects/:project/approvals · GET /services/:service/approvalsCommits esperando
POST /approvals/:id/approve · …/reject
GET·POST /services/:service/repository-accessAcceso a un repositorio de otra persona

Variables, conexiones y dominios

GET /services/:service/variablesNunca incluye el valor de un secreto
PUT /services/:service/variables{ key, value, secret?, sealed? }
POST /services/:service/variables/importUn .env entero
DELETE /variables/:id · POST …/reveal · POST …/sealreveal queda registrado
GET /services/:service/variables/resolvedLas 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 …/:providerIdGET dice a qué podría conectarse; POST { providerId }
GET·POST /services/:service/domainsPOST sin host genera uno
DELETE /domains/:id · POST …/verify

Volúmenes, copias y datos

GET·POST /services/:service/volumesPOST { mountPath }
PATCH /volumes/:id · DELETE{ sizeLimitMb }; DELETE lo desprende, no lo borra
GET·POST /services/:service/backups · PUT …/backups/scheduleCopias y programación
POST /backups/:id/restore · PUT …/lock · DELETE
PUT /services/:service/encryptionCifrado de un bucket
GET /services/:service/data · GET …/data/rowsTablas (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/logUna página desde ?after=, o las últimas líneas con ?tail= (hasta 5000); ?stage=, ?stream= y ?traceId= filtran
GET /deployments/:deployment/log/streamHistorial y luego en vivo, por SSE
GET /services/:service/metrics · GET …/usage?minutes=Series y muestras
GET /services/:service/consumption · GET …/activityConsumo 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/fixClaude arregla un despliegue fallido
GET·POST /services/:service/security-scans · GET /security-scans/:idVulnerabilidades; { 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/costsLa 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/budgetEl presupuesto: { euros: 25 \| "25,50" \| "off", action: notify\|sleep }; responde budget, status, held y sleepPlan
GET /projects/:project/savings · GET /workspaces/:workspace/savingsLas 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-meEl resumen semanal: { facts, subject, html, text }, o mandártelo ya
GET·PUT /projects/:project/status-pageLa 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/:incidentIncidentes: { title, impact, components, state, message }; :incident es su id o su título
POST …/incidents/:incident/updates · POST …/resolve · PATCH·DELETE …/updates/:updateNovedades { state, message, impact }, resolver { message }, corregir o borrar una
GET·POST /projects/:project/status-page/maintenance · DELETE …/maintenance/:idMantenimientos { title, startsAt, endsAt, components, message }; borrar cancela o termina ya
GET /status/:tokenLo 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 · …/imageFeed Atom, insignia SVG y foto del proyecto, sin sesión
GET /platform-status · …/feed.atom · …/badge.svgEl estado de Fundaia, sin sesión
GET /platform-status/manage · …/incidents · …/maintenanceLo mismo que en un proyecto, solo el administrador
GET·POST /projects/:project/monitors · GET·PATCH·DELETE …/monitors/:monitorMonitores 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-addressUna URL de ping nueva para un monitor de cron; la anterior deja de valer
GET·POST·HEAD /ping/:token · …/:token/start · …/:token/failEl 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/replicasLos 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/:alertAlertas: { 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 …/testEnví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 /attentionLo 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/healthUn veredicto por proyecto y cada servicio diagnosticado
GET /services/:service/diagnosis · GET …/dependenciesHallazgos con la siguiente llamada; quién depende de quién
GET /projects/:project/variables/auditReferencias ${{ }} que no resolverán
GET /projects/:project/logs/search?q=service, level, stream, hours, limit
GET /projects/:project/manifestEl proyecto como JSON; ?download=1 lo descarga
GET /deployments/:a/compare/:bMismo código, variables y configuración
POST /services/:service/probe · POST …/wake{ path } contra el contenedor; despertar a mano
GET /projects/:project/workflows/statsTasa de éxito, duración mediana, inestables
GET /streamCambios de estado de todo lo que ves, por SSE
GET /capacityCuá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/refreshLos 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/categoriesLas 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/:idLa 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/voteSin 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 /assistantSi está disponible, el modelo y su precio por millón de tokens
GET·POST /assistant/conversations · GET·PATCH·DELETE …/:idTus conversaciones (guía)
POST /assistant/conversations/:id/messages{ text }: la respuesta llega como flujo SSE
POST /assistant/conversations/:id/proposals/:proposal/confirm · …/dismissDecidir el cambio propuesto; flujo SSE

Novedades

GET /changelogLas 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.atomLas últimas 30 versiones en Atom, para un lector de feeds. Público

Facturación

GET /plansLos planes (público)
GET /workspaces/:id/billing · PUT …/billing/limitEstado y límite de gasto
POST /workspaces/:id/billing/checkout · …/billing/portalSuscribirse y gestionar el pago en Stripe
GET /workspaces/:id/credits · POST …/credits/checkoutSaldo del agente y recargas