Saltar al contenido
AI Workforce OS
ES EN
Crear cuenta

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.

10 min de lectura

En esta página
  1. URL base y versionado
  2. Autenticación
  3. Tokens de API para integraciones
  4. Errores, límites y convenciones
  5. Peticiones, chat, tareas y reportes
  6. Aprobaciones y agentes
  7. Proyectos y skills
  8. Tienda, recompensas y drive
  9. Conexiones, controles y ajustes
  10. Fuentes de API genéricas
  11. WebSocket y health
Volver arriba

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.

URL base y healthBash
# 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.

Login y llamadas autenticadasBash
# 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 rutaDescripción
POST /auth/registerCrea 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/loginInicia sesión. Cuerpo: email, password, org_id opcional.
POST /auth/mfa/verifyCompleta un login que respondió mfa_required. Cuerpo: mfa_token, code.
POST /auth/refreshRota el refresh token (cookie + X-WFAI-CSRF) y devuelve un token de acceso nuevo.
POST /auth/logoutCierra la sesión actual y borra la cookie. Responde 204.
POST /auth/logout-allRevoca todas las sesiones del usuario. Responde 204.
POST /auth/switch-orgAbre una sesión en otra organización del usuario. Cuerpo: org_id.
GET /auth/me, GET /auth/configUsuario, 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: alcance agents:read.
  • GET /requests, GET /requests/{id}: alcance requests:read (también lo cubre requests:write).
  • POST /requests: alcance requests:write. Mismo cuerpo que la API de sesión (text, budget_cap_usd); responde 202 con request_id.
  • GET /reports, GET /reports/{id}: alcance reports:read.
  • GET /usage: alcance usage:read.
Crear y usar un tokenBash
# 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 /activity acepta limit (100 por defecto, máximo 1000).
  • GET /conversations/{id}/messages acepta limit (máximo 200) y before, y devuelve las cabeceras X-Has-More y X-Next-Before.
  • GET /conversations acepta limit y offset y devuelve X-Has-More y X-Next-Offset.
  • GET /rewards/ledger acepta limit (50 por defecto, máximo 200) y before; GET /agents/{id}/memory acepta limit y q.
  • 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.
Formas de errorJSON
// 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ódigoSignificado
400Cuerpo o parámetros inválidos.
401Falta el token, es inválido o expiró.
403El rol no tiene el permiso, o falla la verificación CSRF.
404El recurso no existe en tu organización.
409Conflicto de estado (por ejemplo, una versión esperada distinta o monedas insuficientes).
413Cuerpo demasiado grande.
422Validación fallida en las rutas de /auth/*; fields indica qué campo falló.
429Límite de tasa, o cuenta bloqueada tras varios intentos de login fallidos.
503Funció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.

Enviar una petición y un mensaje de chatBash
# 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
GET /api/v1/requests/{id}JSON
{
  "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 rutaDescripción
POST /requestsCrea 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}/estimateDesglose del costo y estimación previa.
POST /requests/{id}/confirmConfirma o cancela una estimación. Cuerpo: decision (proceed o cancel), budget_cap_usd opcional.
PUT /requests/{id}/budgetCambia el tope (budget_cap_usd; 0 lo quita). Solo admin u owner.
POST /messagesMensaje 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}/messagesConversaciones y sus mensajes.
POST /conversations/{id}/messagesPublica 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}/reassignIntervenir una tarea en curso. Solo admin u owner.
GET /reports, GET /reports/{id}Reportes finales.
GET /activity, GET /metrics, GET /budgetActividad, 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).

Listar y decidir aprobacionesBash
# 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 rutaDescripción
GET /approvalsLista; filtro status (pending, approved, rejected).
POST /approvals/{id}/decisionCuerpo: decision (approve o reject), note, scope (once, session, project), always_allow, always_allow_any_agent, always_allow_days.
POST /approvals/batchDecide varias aprobaciones de un proyecto. Cuerpo: filter (project_id, action), decision, expected_count, include_high.
GET /agents, GET /agents/{id}, GET /agents/{id}/detailAgentes: estado, herramientas, permisos y autonomía.
GET/PUT /agents/{id}/profile, GET /agents/{id}/tracesPerfil 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-templateContratar un agente desde una plantilla de rol.
PUT /agents/{id}/budget, POST /agents/{id}/controlPresupuesto 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.

Crear y lanzar un proyectoBash
# 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 rutaDescripción
GET /projects, POST /projects/draft, POST /projects/from-templateListar y crear borradores. Cuerpo: goal, template_id (obligatorio en from-template), params, budget_usd.
GET /projects/{id}, /snapshot, /health, /approvalsEstado, salud y aprobaciones del proyecto.
PATCH /projects/{id}/plan, POST /projects/{id}/estimate, /validate, /launchEditar, estimar, validar y lanzar. launch exige approved_budget_usd mayor que 0.
POST /projects/{id}/control, PUT /projects/{id}/budgetPausar, reanudar o cancelar (action) y fijar el presupuesto (budget_usd).
POST /projects/{id}/nodes/{nodeId}/retry, /skip, /replanRecuperar 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, /disableFlujo 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.

Cartera, compra y subidaBash
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 rutaDescripción
GET /rewards/wallet, /rewards/ledger, /rewards/rulesSaldo, movimientos y reglas de pago.
GET /shop/catalog, GET /shop/stateCatálogo y estado (inventario, equipado, oficina).
POST /shop/purchasesComprar. 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/trashExplorar 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, /diffLeer 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.

Pausar un agente y activar el kill switchBash
# 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 rutaDescripción
GET /connection-providers, GET/POST /connectionsProveedores disponibles y conexiones.
PATCH /connections/{id}, PUT /connections/{id}/credentialEditar y guardar la credencial (solo escritura).
POST /connections/{id}/oauth/start, POST /connections/{id}/testIniciar OAuth y probar la conexión.
GET /connections/oauth/callbackCallback público de OAuth, verificado por un estado de un solo uso.
GET/PUT /org/controls, POST /org/controls/kill-switch, /releaseModo de la organización (normal, read_only) y kill switch (freeze, lockdown).
GET/PUT /org/operating-hours, /org/anomaly-settingsHorario operativo y ajustes de anomalías.
GET/PUT /org/settings, GET/POST /onboardingAjustes 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-executionPolítica de modelos y ejecución de código (owner o admin).
GET /audit, /audit/export, /audit/verifyAuditorí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.

Crear una fuente de API de solo lecturaBash
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).

Conectar al WebSocketBash
# 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": { } }