Referencia de la API
El backend en Go expone una API REST bajo /api/v1 y un canal de eventos en /ws. Esta referencia es para quien integra o automatiza: cubre autenticación, convenciones de errores y las rutas principales tal como están registradas en el router, con ejemplos que puedes copiar.
En esta página
- URL base y versionado
- Autenticación
- Tokens de API para integraciones
- Errores, límites y convenciones
- Peticiones, chat, tareas y reportes
- Aprobaciones y agentes
- Proyectos y skills
- Tienda, recompensas y drive
- Conexiones, controles y ajustes
- Fuentes de API genéricas
- WebSocket y health
URL base y versionado#
Todas las rutas REST cuelgan de /api/v1; la versión forma parte de la ruta. El WebSocket (/ws) y el health check raíz (/healthz) quedan fuera de ese prefijo. Los cuerpos son JSON en UTF-8, salvo las subidas al drive, que envían el archivo como cuerpo crudo.
Los ejemplos usan localhost:8080; sustituye por tu host. Todas las rutas de esta página son relativas a /api/v1 salvo que se indique otra cosa.
# REST base URL (local default)
http://localhost:8080/api/v1
# WebSocket (outside /api/v1)
ws://localhost:8080/ws
# Health check: no token needed
curl http://localhost:8080/healthz
# {"status":"ok","runtime":"up","runtime_mode":"simulation"}
Atención
Con AUTH_ENABLED=false (modo demo) la API no exige autenticación y todo opera sobre una organización fija. No expongas ese modo a una red.
Autenticación#
Con AUTH_ENABLED=true, toda ruta exige un token salvo /healthz, las rutas públicas de /auth/*, el callback de OAuth y unos pocos enlaces que llevan su propio secreto (enlaces firmados de aprobación y el feed de calendario).
El login devuelve un token de acceso (JWT, 15 minutos) que se envía en la cabecera Authorization: Bearer. El refresh token nunca va en el cuerpo JSON: viaja en la cookie HttpOnly y SameSite=Strict wfai_refresh, limitada a la ruta /api/v1/auth. Refresh y logout actúan sobre esa cookie, así que exigen además la cabecera X-WFAI-CSRF.
La organización sale del token, nunca de un parámetro. Los roles son owner, admin, member y viewer, y cada ruta exige un permiso concreto.
# Sign in: access token in the body, refresh token in an HttpOnly cookie
curl -i -X POST http://localhost:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","password":"<password>"}'
# 200 {"token_type":"Bearer","access_token":"<token>","expires_in":900,
# "expires_at":"...","user":{...},"org_id":"<org id>","role":"owner"}
# Call the API with the access token
curl http://localhost:8080/api/v1/agents -H 'Authorization: Bearer <token>'
# Renew the session: cookie + CSRF header
curl -X POST http://localhost:8080/api/v1/auth/refresh \
-b 'wfai_refresh=<cookie value>' -H 'X-WFAI-CSRF: 1'
| Método y ruta | Descripción |
|---|---|
POST /auth/register | Crea organización y usuario owner. Cuerpo: email, password, name, org_name. Responde 201 con la sesión, o 202 con verification_required si la verificación de correo está activa. |
POST /auth/login | Inicia sesión. Cuerpo: email, password, org_id opcional. |
POST /auth/mfa/verify | Completa un login que respondió mfa_required. Cuerpo: mfa_token, code. |
POST /auth/refresh | Rota el refresh token (cookie + X-WFAI-CSRF) y devuelve un token de acceso nuevo. |
POST /auth/logout | Cierra la sesión actual y borra la cookie. Responde 204. |
POST /auth/logout-all | Revoca todas las sesiones del usuario. Responde 204. |
POST /auth/switch-org | Abre una sesión en otra organización del usuario. Cuerpo: org_id. |
GET /auth/me, GET /auth/config | Usuario, rol, permisos y organizaciones; configuración pública (si hay login, registro, verificación, prueba, MFA). |
GET/POST /auth/members, PATCH/DELETE /auth/members/{id} | Listar, agregar (email, role), cambiar el rol o quitar miembros. |
GET/POST /invitations, DELETE /invitations/{id} | Gestión de invitaciones. POST /auth/invitations/accept las acepta. |
Nota
Si el usuario tiene verificación en dos pasos, POST /auth/login responde 200 con mfa_required y un mfa_token en lugar de la sesión. Los detalles de cuentas, roles y sesiones están en Autenticación y multitenencia.
Tokens de API para integraciones#
Para integraciones de servidor a servidor existe una API aparte en /api/public/v1, autenticada con tokens de API con alcances en lugar de sesiones de usuario. La organización sale del token. Requiere Postgres; sin él estas rutas no se montan.
Un admin crea, lista y revoca tokens con POST/GET /governance/tokens y DELETE /governance/tokens/{id}. Cuerpo de creación: name, scopes, expires_in_days. Cada token admite 120 peticiones por minuto; al excederlo responde 429 con Retry-After.
GET /agents: alcanceagents:read.GET /requests,GET /requests/{id}: alcancerequests:read(también lo cubrerequests:write).POST /requests: alcancerequests:write. Mismo cuerpo que la API de sesión (text,budget_cap_usd); responde 202 conrequest_id.GET /reports,GET /reports/{id}: alcancereports:read.GET /usage: alcanceusage:read.
# Create a token (admin session); the plaintext is returned once
curl -X POST http://localhost:8080/api/v1/governance/tokens \
-H 'Authorization: Bearer <session token>' -H 'Content-Type: application/json' \
-d '{"name":"crm-sync","scopes":["requests:write","reports:read"],"expires_in_days":90}'
# 201 {"token":"<api token>","item":{...},"note":"shown once"}
# Use it against the public API
curl -X POST http://localhost:8080/api/public/v1/requests \
-H 'Authorization: Bearer <api token>' -H 'Content-Type: application/json' \
-d '{"text":"Summarize the open deals of this week"}'
# 202 {"request_id":"<id>"}
Atención
El token en texto plano se devuelve una sola vez, al crearlo. Guárdalo en tu gestor de secretos; si se filtra, revócalo con DELETE /governance/tokens/{id}.
Errores, límites y convenciones#
Los errores devuelven JSON con un código HTTP significativo. Hay tres formas según la ruta; un 500 nunca expone el detalle interno.
- Sin paginación global: cada listado define sus parámetros.
GET /activityaceptalimit(100 por defecto, máximo 1000).GET /conversations/{id}/messagesaceptalimit(máximo 200) ybefore, y devuelve las cabecerasX-Has-MoreyX-Next-Before.GET /conversationsaceptalimityoffsety devuelveX-Has-MoreyX-Next-Offset.GET /rewards/ledgeraceptalimit(50 por defecto, máximo 200) ybefore;GET /agents/{id}/memoryaceptalimityq.- Los cuerpos JSON tienen un tope de 1 MiB por defecto (
MAX_BODY_BYTES); al excederlo la respuesta es 413. - Las rutas públicas de autenticación tienen límites de tasa por IP y responden 429 con
Retry-After.
// Most routes
{ "error": "invalid input: text is required" }
// Some domains (connections, controls, drive, shop...) add a stable code
{ "error": "conflict: not enough coins", "code": "insufficient_coins" }
// /auth/* routes and permission failures
{ "error": { "code": "forbidden", "message": "insufficient permissions" } }
// Auth validation (422) lists the fields that failed
{ "error": { "code": "validation_failed", "message": "invalid input",
"fields": { "password": "..." } } }
| Código | Significado |
|---|---|
| 400 | Cuerpo o parámetros inválidos. |
| 401 | Falta el token, es inválido o expiró. |
| 403 | El rol no tiene el permiso, o falla la verificación CSRF. |
| 404 | El recurso no existe en tu organización. |
| 409 | Conflicto de estado (por ejemplo, una versión esperada distinta o monedas insuficientes). |
| 413 | Cuerpo demasiado grande. |
| 422 | Validación fallida en las rutas de /auth/*; fields indica qué campo falló. |
| 429 | Límite de tasa, o cuenta bloqueada tras varios intentos de login fallidos. |
| 503 | Función no configurada en el servidor, por ejemplo kek_missing al guardar credenciales sin CONNECTIONS_KEK. |
Consejo
POST /messages y POST /shop/purchases aceptan la cabecera Idempotency-Key: un reintento con la misma clave devuelve el primer resultado en lugar de repetir la acción.
Peticiones, chat, tareas y reportes#
POST /requests es asíncrono: responde 202 con el request_id y el avance llega por el WebSocket. budget_cap_usd es opcional y fija un tope duro para esa petición. GET /requests/{id} devuelve la petición con sus tareas, el plan y el costo acumulado.
Estados de una petición: planning, running, awaiting_approval, done, failed. Estados de una tarea: pending, running, blocked, awaiting_approval, done, failed.
# Submit a request: asynchronous, progress arrives over the WebSocket
curl -X POST http://localhost:8080/api/v1/requests \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"text":"Prepare a $50,000 proposal for ACME","budget_cap_usd":5}'
# 202 {"request_id":"<id>"}
# Chat with the office (a retry with the same key returns the first turn)
curl -X POST http://localhost:8080/api/v1/messages \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7d1e...' \
-d '{"conversation":"office","text":"Who is free today?"}'
# 202 turn object; replies arrive as chat.message events
{
"id": "<request id>",
"text": "Prepare a $50,000 proposal for ACME",
"status": "running",
"created_at": "2026-01-01T12:00:00Z",
"report_id": null,
"cost_usd": 0.0123,
"tasks": [
{
"id": "<task id>",
"request_id": "<request id>",
"workflow_id": null,
"title": "Draft the commercial proposal",
"description": "...",
"agent_id": "sales",
"status": "running",
"depends_on": [],
"parent_task_id": null,
"created_at": "2026-01-01T12:00:02Z",
"started_at": "2026-01-01T12:00:03Z",
"finished_at": null,
"output": null
}
],
"plan": {
"objectives": ["..."],
"clarifying_questions": [],
"tasks": [
{ "id": "<task id>", "title": "Draft the commercial proposal",
"agent_id": "sales", "depends_on": [] }
]
},
"budget_cap_usd": 5
}
| Método y ruta | Descripción |
|---|---|
POST /requests | Crea una petición. Cuerpo: text, budget_cap_usd opcional. |
GET /requests, GET /requests/{id} | Listar y ver el detalle (tareas, plan, costo y tope). |
GET /requests/{id}/cost, GET /requests/{id}/estimate | Desglose del costo y estimación previa. |
POST /requests/{id}/confirm | Confirma o cancela una estimación. Cuerpo: decision (proceed o cancel), budget_cap_usd opcional. |
PUT /requests/{id}/budget | Cambia el tope (budget_cap_usd; 0 lo quita). Solo admin u owner. |
POST /messages | Mensaje de chat. Cuerpo: conversation (office por defecto, o agent:<id>), text, attachments (rutas del drive, máximo 6). Responde 202. |
GET /conversations, GET /conversations/{id}/messages | Conversaciones y sus mensajes. |
POST /conversations/{id}/messages | Publica en una conversación concreta. Cuerpo: text. Responde 201. |
GET /tasks, GET /tasks/{id} | Tareas; filtros agent_id y status. |
GET /tasks/{id}/intervention, POST /tasks/{id}/steer, POST /tasks/{id}/reassign | Intervenir una tarea en curso. Solo admin u owner. |
GET /reports, GET /reports/{id} | Reportes finales. |
GET /activity, GET /metrics, GET /budget | Actividad, métricas y estado del presupuesto. |
Aprobaciones y agentes#
Decidir una aprobación es una acción privilegiada: solo admin u owner. Al aprobar puedes pedir además un permiso permanente (always_allow) para acciones internas y reversibles; si no es elegible, la aprobación se mantiene y la respuesta incluye standing_error. Una aprobación puede exigir dos personas distintas (required_approvals).
# List pending approvals
curl 'http://localhost:8080/api/v1/approvals?status=pending' \
-H 'Authorization: Bearer <token>'
# Approve (admin or owner)
curl -X POST http://localhost:8080/api/v1/approvals/<id>/decision \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"decision":"approve","note":"ok"}'
# 200 the updated approval; "decision" is "approve" or "reject"
| Método y ruta | Descripción |
|---|---|
GET /approvals | Lista; filtro status (pending, approved, rejected). |
POST /approvals/{id}/decision | Cuerpo: decision (approve o reject), note, scope (once, session, project), always_allow, always_allow_any_agent, always_allow_days. |
POST /approvals/batch | Decide varias aprobaciones de un proyecto. Cuerpo: filter (project_id, action), decision, expected_count, include_high. |
GET /agents, GET /agents/{id}, GET /agents/{id}/detail | Agentes: estado, herramientas, permisos y autonomía. |
GET/PUT /agents/{id}/profile, GET /agents/{id}/traces | Perfil de trabajo versionado y trazas de razonamiento (estas requieren audit:read). |
GET /agents/{id}/memory, DELETE /agents/{id}/memory/{docId} | Memoria a largo plazo. |
GET /role-templates, POST /agents/from-template | Contratar un agente desde una plantilla de rol. |
PUT /agents/{id}/budget, POST /agents/{id}/control | Presupuesto mensual (monthly_budget_usd) y pausa, reanudación o solo lectura. |
Proyectos y skills#
Un proyecto es un plan de varios nodos con presupuesto. El ciclo es crear un borrador, editar el plan, estimar y validar, y lanzar con un presupuesto aprobado. Pausar, cancelar y cambiar el presupuesto de un proyecto en curso son acciones de admin.
Las skills son instrucciones reutilizables y versionadas. Los miembros crean y editan borradores; solo un admin las revisa, activa, desactiva o borra.
# Create a draft project
curl -X POST http://localhost:8080/api/v1/projects/draft \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"goal":"Launch the spring campaign","budget_usd":200}'
# 201 {"project_id":"<id>"}
# Launch it with an approved budget (required, greater than 0)
curl -X POST http://localhost:8080/api/v1/projects/<id>/launch \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"approved_budget_usd":200}'
# 202 {"ok":true,"project":{...}}
| Método y ruta | Descripción |
|---|---|
GET /projects, POST /projects/draft, POST /projects/from-template | Listar y crear borradores. Cuerpo: goal, template_id (obligatorio en from-template), params, budget_usd. |
GET /projects/{id}, /snapshot, /health, /approvals | Estado, salud y aprobaciones del proyecto. |
PATCH /projects/{id}/plan, POST /projects/{id}/estimate, /validate, /launch | Editar, estimar, validar y lanzar. launch exige approved_budget_usd mayor que 0. |
POST /projects/{id}/control, PUT /projects/{id}/budget | Pausar, reanudar o cancelar (action) y fijar el presupuesto (budget_usd). |
POST /projects/{id}/nodes/{nodeId}/retry, /skip, /replan | Recuperar un nodo fallido o bloqueado. |
GET/POST /project-templates, GET/PUT/DELETE /project-templates/{id} | Plantillas con versiones. |
GET/POST /skills, GET/PUT /skills/{id} | Catálogo y edición de skills. |
POST /skills/{id}/submit, /review, /disable | Flujo de revisión; review y disable requieren skills:review. |
GET /agents/{id}/skills, PUT/DELETE /agents/{id}/skills/{skillId} | Asignar skills a un agente. |
Atención
Lanzar un proyecto empieza a gastar contra approved_budget_usd. Revisa POST /projects/{id}/estimate antes de lanzar.
Tienda, recompensas y drive#
El trabajo terminado paga monedas a la cartera de la organización. Una compra es idempotente: la misma Idempotency-Key (o idempotency_key en el cuerpo) devuelve el resultado original sin cobrar de nuevo. Sin saldo suficiente, la respuesta es 409 con insufficient_coins.
El drive guarda los archivos de la organización con versiones, papelera y permisos por carpeta. Una subida crea el archivo (201) o le agrega una versión (200); con expected_version obtienes 409 version_conflict si alguien lo cambió antes.
curl http://localhost:8080/api/v1/rewards/wallet -H 'Authorization: Bearer <token>'
curl -X POST http://localhost:8080/api/v1/shop/purchases \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-H 'Idempotency-Key: <unique key>' \
-d '{"item_id":"<item id>"}'
# 201 first purchase; 200 when the same key is replayed
# Upload a file to the drive (raw body)
curl -X PUT 'http://localhost:8080/api/v1/drive/upload?path=/Shared/plan.xlsx' \
-H 'Authorization: Bearer <token>' --data-binary @plan.xlsx
# 201 created, 200 new version
| Método y ruta | Descripción |
|---|---|
GET /rewards/wallet, /rewards/ledger, /rewards/rules | Saldo, movimientos y reglas de pago. |
GET /shop/catalog, GET /shop/state | Catálogo y estado (inventario, equipado, oficina). |
POST /shop/purchases | Comprar. Cuerpo: item_id, idempotency_key opcional. Responde 201, o 200 si es un reintento. |
PUT/DELETE /agents/{id}/equipment/{slot} | Equipar o quitar un objeto del personaje. Cuerpo: item_id. |
PUT/DELETE /office/items/{slot} | Colocar o retirar un objeto de la oficina. |
GET /drive/list, /drive/search, /drive/usage, /drive/trash | Explorar el drive. |
PUT /drive/upload?path= | Subir o versionar un archivo (cuerpo crudo). Opcionales: message, expected_version. |
GET /drive/files/{id}, /content, /text, /preview, /versions, /diff | Leer un archivo y su historial. |
POST /drive/folders, POST /drive/files/{id}/move, DELETE /drive/files/{id} | Organizar y borrar (a la papelera). |
Nota
El drive tiene más rutas: restaurar versiones y archivos, etiquetas (PUT /drive/files/{id}/tags) y permisos de agentes sobre carpetas (/drive/grants).
Conexiones, controles y ajustes#
Las conexiones guardan credenciales cifradas de servicios externos. Los controles permiten pausar agentes, poner la organización en solo lectura o activar un kill switch; levantarlo (POST /org/controls/release) es exclusivo del owner.
# Pause one agent (action: pause | resume | read_only | read_write)
curl -X POST http://localhost:8080/api/v1/agents/<id>/control \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"action":"pause","drain":"graceful","reason":"maintenance"}'
# Organization kill switch (level: freeze | lockdown)
curl -X POST http://localhost:8080/api/v1/org/controls/kill-switch \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"level":"freeze","reason":"incident"}'
| Método y ruta | Descripción |
|---|---|
GET /connection-providers, GET/POST /connections | Proveedores disponibles y conexiones. |
PATCH /connections/{id}, PUT /connections/{id}/credential | Editar y guardar la credencial (solo escritura). |
POST /connections/{id}/oauth/start, POST /connections/{id}/test | Iniciar OAuth y probar la conexión. |
GET /connections/oauth/callback | Callback público de OAuth, verificado por un estado de un solo uso. |
GET/PUT /org/controls, POST /org/controls/kill-switch, /release | Modo de la organización (normal, read_only) y kill switch (freeze, lockdown). |
GET/PUT /org/operating-hours, /org/anomaly-settings | Horario operativo y ajustes de anomalías. |
GET/PUT /org/settings, GET/POST /onboarding | Ajustes de la organización y onboarding. |
GET /settings/llm, PUT/DELETE /settings/llm/providers/{provider} | Credenciales propias de modelo (owner o admin). |
GET/PUT /settings/model-policy, /settings/code-execution | Política de modelos y ejecución de código (owner o admin). |
GET /audit, /audit/export, /audit/verify | Auditoría encadenada, exportación y verificación (audit:read). |
Atención
Sin CONNECTIONS_KEK el servidor no puede guardar credenciales reales: esas rutas responden 503 kek_missing y solo funcionan las conexiones simuladas. Las claves propias de modelo (/settings/llm) también lo necesitan.
Fuentes de API genéricas#
Fuentes de API genéricas: una conexión custom_api con un objeto source convierte cualquier plataforma con API HTTP y clave (un ERP, un CRM, una API interna) en algo que los agentes consultan sin escribir un adaptador. Solo envía GET; si defines endpoints, solo esos se pueden llamar y solo viajan los parámetros listados. Los agentes la piden como api.get con connection_alias y reciben el resultado como datos no confiables. Cada tarea le dice al agente qué fuentes tiene concedidas.
curl -X POST http://localhost:8080/api/v1/connections \n -H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \n -d '{"provider":"custom_api","kind":"api_key","label":"ERP","secret":"<api-key>",
"source":{"base_url":"https://erp.example.com/api",
"auth":{"style":"header","name":"X-API-Key"},
"description":"Inventory, orders and customers",
"endpoints":[{"name":"stock","path":"/products/{sku}/stock","params":["warehouse"]}]}}'
# What an agent asks for (the backend runs it; the agent never sees the key):
# {"tool":"api","action":"get","args":{"connection_alias":"erp","endpoint":"stock","params":{"sku":"A-1"}}}
WebSocket y health#
GET /ws abre el canal de eventos de tu organización. Al conectar recibes hello con la lista de agentes y luego cada evento de dominio; un evento con audiencia solo llega a los usuarios indicados. No hay suscripciones por tema ni replay: tras reconectar, recarga el estado por REST.
El servidor envía ping cada 30 s y cierra si no recibe pong en 60 s. Solo procesa del cliente pequeños cuadros de presencia (posición, estado) cuando esa función está activa; el resto se ignora. Con autenticación activa, el servidor cierra el socket con el código 4401 a los 15 minutos (la vida del token de acceso) y el cliente debe reconectar con un token nuevo.
GET /healthz y GET /api/v1/healthz no requieren autenticación. Devuelven status, runtime (up o down) y, si el runtime responde, runtime_mode (simulation o live).
# Browsers cannot set headers on a WebSocket: pass the token in the query string
wscat -c 'ws://localhost:8080/ws?access_token=<token>'
< {"id":"...","type":"hello","ts":"...","org_id":"<org id>","payload":{"agents":[...]}}
< {"id":"...","type":"agent.state_changed","ts":"...","org_id":"<org id>","agent_id":"sales","payload":{...}}
# Frame shape
{ "id": "<uuid>", "type": "<event.type>", "ts": "<RFC 3339>",
"org_id": "<org id>", "agent_id": "<optional>", "payload": { } }