Documentación de la API
Sandboxes, web, archivos, bases de datos, documentos y hosting para agentes de IA
URL base: https://www.easybits.cloud/api/v2
Inicio rápido
- Crea una cuenta en easybits.cloud
- Ve al Dashboard de Desarrollador y crea una API key
- Haz tu primera llamada:
Obtén tu API key. Por defecto cargan solo el grupo core, para no gastar contexto. Agrega --tools docs,slides,all para más. Ver tool groups.
Authentication
All API requests require a Bearer token in the Authorization header.
What your key grants access to
An EasyBits API key authenticates you as the owner of your account. It grants access to all your resources: files, websites, databases, webhooks, documents, presentations, and landings. Keep it secret — anyone with your key can read, modify, or delete your data.
Scopes
Each key is created with one or more scopes. Use the most restrictive scope your integration needs.
| Scope | Allows |
|---|---|
| READ | List and get files, websites, documents, webhooks, and usage stats |
| WRITE | Create, upload, update, optimize, transform, and share files. Create websites, webhooks, databases, documents, and presentations |
| DELETE | Soft-delete and permanently remove files, websites, webhooks, and other resources |
| ADMIN | Full access including key management, provider configuration, sandbox/agent operations, and account-wide actions |
Keys created from the Developer Dashboard default to READ + WRITE + DELETE. Use the API to create scoped keys programmatically.
Ghosty Code
El runtime agéntico con EasyBits preinstalado. Cero configuración.
Ghosty Code trae el MCP de EasyBits preconfigurado.
Viene desactivado de fábrica hasta que añades tu API key — una instalación nueva nunca falla por falta de credencial.
Conexión en 3 pasos
- Instala el CLI:
npm install -g ghostycode(ocurl -fsSL https://formmy.app/ghosty/install.sh | sh) - Autentica con tu key de EasyBits (sirve para LLM + MCP):
ghosty auth set --provider easybits --api-key "TU_EASYBITS_API_KEY" - Ejecuta:
ghosty --yolo
Consigue tu API key en /dash/developer. Verifica el setup con ghosty doctor y los MCPs con ghosty mcp list.
Agregar EasyBits manualmente
Si necesitas (re)agregar el servidor MCP con tu key:
Qué incluye
248 herramientas: sandboxes, web, archivos, DBs, documentos y hosting
Modelo principal con razonamiento profundo (thinking tokens)
BrightData integrado para búsquedas y scraping
Agrega y quita servidores MCP en runtime sin reiniciar
Firecracker microVMs para ejecutar código y agentes aislados
ghosty update para mantener todo al día
ghosty update — el MCP de EasyBits ya viene preconfigurado.¿No usas Ghosty Code? EasyBits funciona con Claude Cowork, Cursor, VS Code y cualquier cliente MCP. Ver todas las opciones de conexión.
Claude Cowork (OAuth)
For Claude.ai, Cowork, and other web-based MCP clients that can't store API keys.
EasyBits implements OAuth 2.1 with Dynamic Client Registration (RFC 7591) and PKCE S256. Web MCP clients discover, register, and authenticate automatically — no API key copying, no JSON configs.
Connect in 4 steps
- In Cowork, open Settings → Connectors → Add custom connector
- Paste the MCP URL:
https://www.easybits.cloud/api/mcp(or pick a toolset:/api/mcp/sandbox) - Click Connect — you'll be redirected to EasyBits to log in
- Authorize the connector. You're done — the agent has access to your workspace
?tools=all to the URL to expose all 248 tools instead of the core group. See Tool Groups for other options.How it works
EasyBits exposes the standard OAuth discovery endpoints so any spec-compliant MCP client connects without manual setup:
| Endpoint | Spec | Purpose |
|---|---|---|
| /.well-known/oauth-protected-resource[/<resource path>] | RFC 9728 | Tells clients which Authorization Server protects /api/mcp. Ask for the toolset path (e.g. /api/mcp/sandbox) and it answers for that exact resource |
| /.well-known/oauth-authorization-server | RFC 8414 | Advertises authorize, token, and registration endpoints |
| /oauth/register | RFC 7591 | Dynamic Client Registration — client_id + secret issued on POST |
| /oauth/authorize | OAuth 2.1 | User consent + code issuance (PKCE S256 required) |
| /oauth/token | OAuth 2.1 | Exchanges code + verifier for a 1-hour JWT access token |
Handshake flow
Notes
- Access tokens are HS256 JWTs, valid for 1 hour. No refresh token — reauthorize is a single click when you already have a session.
- Auto-approval: once logged in, the authorize screen redirects back immediately. The user already expressed consent by initiating the flow from the connector.
- Additive: API key Bearer auth keeps working unchanged. The handler tries JWT verification first and silently falls through to API key validation.
- PKCE S256 is mandatory. Plain and no-PKCE flows are rejected.
- Scope: a single
mcpscope — the authorized session has full access to the MCP handler.
SDK
El SDK tipado envuelve toda la REST API. Instálalo y úsalo en cualquier proyecto Node.js/Bun/Deno.
Todos los métodos
Archivos
| Method | Description |
|---|---|
| listFiles(params?) | Lista archivos (paginado) |
| getFile(fileId) | Obtén el archivo + URL de descarga |
| uploadFile(params) | Crea el archivo + obtén URL de subida |
| updateFile(fileId, params) | Actualiza nombre, acceso, metadata, status |
| deleteFile(fileId) | Borrado suave (retención 7 días) |
| restoreFile(fileId) | Restaura desde la papelera |
| listDeletedFiles(params?) | Lista la papelera con días hasta la purga |
| searchFiles(query) | Búsqueda en lenguaje natural con IA |
| duplicateFile(fileId, name?) | Copia el archivo (nuevo objeto de storage) |
| listPermissions(fileId) | Lista los permisos de compartición |
Operaciones en lote
| Method | Description |
|---|---|
| bulkUploadFiles(items) | Sube hasta 20 archivos a la vez |
| bulkDeleteFiles(fileIds) | Borra hasta 100 archivos a la vez |
Imágenes
| Method | Description |
|---|---|
| optimizeImage(params) | Convierte a WebP/AVIF |
| transformImage(params) | Redimensiona, rota, voltea, convierte, escala de grises |
Compartir
| Method | Description |
|---|---|
| shareFile(params) | Comparte con otro usuario por email |
| generateShareToken(fileId, expiresIn?) | URL de descarga temporal |
| listShareTokens(params?) | Lista tokens (paginado) |
Formularios
| Method | Description |
|---|---|
| createForm(params) | Crea un formulario hospedado (/f/:slug) |
| listForms() | Lista tus formularios con conteo de respuestas |
| getForm(formId) | Obtén la config del formulario (campos, theme) |
| updateForm(formId, patch) | Actualiza nombre, theme, campos o mensaje |
| getFormSubmissions(formId, opts?) | Lista las respuestas de un formulario |
Webhooks
| Method | Description |
|---|---|
| listWebhooks() | Lista los webhooks configurados |
| createWebhook(params) | Crea un webhook (devuelve el secret una vez) |
| getWebhook(webhookId) | Obtén los detalles del webhook |
| updateWebhook(webhookId, params) | Actualiza URL, eventos o status |
| deleteWebhook(webhookId) | Borra permanentemente |
Sitios web
| Method | Description |
|---|---|
| listWebsites() | Lista los sitios estáticos |
| createWebsite(name) | Crea un sitio, obtén id + URL |
| getWebsite(websiteId) | Obtén los detalles del sitio |
| updateWebsite(websiteId, params) | Actualiza nombre/status |
| deleteWebsite(websiteId) | Borra el sitio + archivos |
Despliega archivos subiéndolos con fileName: "sites/{websiteId}/path" — ve la sección Sitios web para el ejemplo completo.
Cuenta
| Method | Description |
|---|---|
| getUsageStats() | Storage, conteo de archivos, info del plan |
| listProviders() | Proveedores de storage |
| listKeys() | API keys |
Manejo de errores
Archivos
/filesLista tus archivos (paginado)
| assetId | string | Filtra por ID de asset |
| limit | number | Máx resultados (default 50, máx 100) |
| cursor | string | Cursor de paginación |
| status | string | Pon 'DELETED' para listar archivos borrados |
/files/:fileIdObtén los detalles del archivo con una URL de descarga temporal
/filesCrea un registro de archivo y obtén una URL de subida prefirmada
| fileName | string | Requerido |
| contentType | string | Tipo MIME (requerido) |
| size | number | Tamaño en bytes (requerido, 1B–5GB) |
| access | string | 'public' o 'private' (default) |
| region | string | 'LATAM', 'US' o 'EU' |
Sube los bytes con PUT a putUrl, luego haz PATCH del status del archivo a 'DONE'.
/files/:fileIdActualiza nombre, nivel de acceso, metadata o status del archivo
| name | string | Nuevo nombre |
| access | string | 'public' o 'private' |
| metadata | object | Pares clave-valor (se fusionan, máx 10KB) |
| status | string | Solo 'DONE' (desde PENDING) |
/files/:fileIdBorrado suave (retención de 7 días)
/files/:fileId/restoreRestaura un archivo borrado (soft-delete)
/files/search?q=...Búsqueda de archivos en lenguaje natural con IA (requiere AI key)
| q | string | Consulta en lenguaje natural (requerida) |
/files/:fileId/duplicateCrea una copia de un archivo existente (nuevo objeto de storage)
| name | string | Nombre de la copia (opcional, default 'Copy of ...') |
/files/:fileId/permissionsLista los permisos de compartición de un archivo
Operaciones en lote
/files/bulk-uploadCrea varios registros de archivo y obtén URLs de subida prefirmadas (máx 20)
| items | array | Arreglo de { fileName, contentType, size, access? } |
Cada archivo se sube con PUT a su putUrl, luego se pone el status en DONE.
/files/bulk-deleteBorra varios archivos a la vez (soft-delete, máx 100)
| fileIds | string[] | Arreglo de IDs de archivo a borrar |
Imágenes
/stock-photosBusca una foto libre de regalías en bancos gratuitos (Pexels → Unsplash → Pixabay → Openverse) y devuelve la primera coincidencia
| q | string | Qué buscar (requerido). En inglés da mejores resultados en todos los bancos |
| save | boolean | Guarda la foto en tu biblioteca y añade fileId + savedUrl. Requiere scope WRITE; buscar sin guardar, no |
Cuesta 1 crédito por llamada, también cuando la coincidencia es mala: la búsqueda es difusa y casi siempre devuelve algo, así que revisa `alt` para juzgar la relevancia en vez de esperar un error. Debes mostrar `attribution`: Unsplash y Pixabay exigen acreditar al autor en sus términos.
search_stock_photoMCPquery, save?
Busca una foto de stock libre de regalías y devuelve su URL. Con save guarda una copia en la biblioteca. Cuesta 1 crédito; muestra siempre attribution.
/files/:fileId/optimizeConvierte la imagen a WebP o AVIF (crea un archivo nuevo)
| format | string | 'webp' (default) o 'avif' |
| quality | number | 1–100 (default: 80 webp, 50 avif) |
/files/:fileId/transformRedimensiona, recorta, rota, voltea o convierte una imagen (crea un archivo nuevo)
| width | number | Ancho objetivo en px |
| height | number | Alto objetivo en px |
| fit | string | 'cover', 'contain', 'fill', 'inside', 'outside' |
| format | string | 'webp', 'avif', 'png', 'jpeg' |
| quality | number | 1–100 |
| rotate | number | Grados |
| flip | boolean | Voltea vertical |
| grayscale | boolean | Convierte a escala de grises |
Web
Internet para tus agentes: buscar en Google, leer cualquier página aunque bloquee bots (IPs residenciales, JS resuelto), extraer registros con esquema de sitios conocidos y rastrear un sitio completo. Disponible por REST, SDK y MCP (toolset web).
402.country es opcional y son 2 letras (ISO 3166-1): mx México · us Estados Unidos · es España · ar Argentina · co Colombia · cl Chile · pe Perú · br Brasil. Sirve para ver el sitio como un usuario de ese país (precios en MXN, stock local, resultados de Google localizados). Si lo omites, el proveedor elige. Lista completa: ISO 3166-1. En web_extract con google_maps va en MAYÚSCULAS dentro del input (country: "MX")./web/searchBusca en Google (o Bing/Yandex/DuckDuckGo) y devuelve resultados estructurados: orgánicos, negocios locales, knowledge panel
| query | string | Texto plano (requerido) |
| engine | string | google (default) | bing | yandex | duckduckgo |
| country | string | País en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo |
1 consulta. Úsalo para encontrar la URL correcta y luego léela con /web/fetch.
/web/fetchLee una página aunque bloquee bots. Devuelve HTML o markdown
| url | string | https://… (requerido) |
| country | string | País en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo |
| asMarkdown | boolean | true → markdown en vez de HTML |
| onlyMainContent | boolean | Con asMarkdown: quita nav, footer, iconos y 'skip to content'. Lo normal cuando vas a leer la página |
1 consulta. El cuerpo se recorta a 200 KB.
/web/extractExtrae registros con esquema estable de una fuente conocida. Asíncrono: devuelve un job
| source | string | google_maps | mercadolibre | amazon_product | amazon_reviews | google_shopping | instagram_profiles | instagram_posts | tiktok_profiles | tiktok_posts | facebook_page_posts | facebook_marketplace | youtube_channels | youtube_videos | linkedin_company | linkedin_person | linkedin_jobs | indeed_jobs | trustpilot | inmuebles24 | reddit_posts |
| datasetId | string | Para fuentes fuera de la lista (catálogo de +1,000) |
| input | object | object[] | google_maps → [{ keyword, country: 'MX' }] · mercadolibre → { query, page? } · resto → [{ url }] |
| limit | number | Registros máximos por input (default 20, máx 200) |
Cobra 1 consulta POR REGISTRO devuelto, una sola vez, al recogerlos. Disparar el job no cuesta; un job que falla no cobra.
/web/extract/:jobIdEstado de un job de extract; cuando termina trae los registros
Gratis mientras corre. Volver a pedir un job ya cobrado no cobra de nuevo. Los jobs con esquema tardan 30-120 s: haz poll cada ~15 s.
/web/crawlLee una página y sigue sus links internos (mismo dominio) hasta maxPages
| url | string | URL de inicio (requerido) |
| maxPages | number | 1-20, default 10. Cada página leída cuesta 1 consulta |
| onlyMainContent | boolean | Quita nav, footer e iconos de cada página (recomendado para RAG) |
| country | string | País en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo |
1 consulta por página realmente leída. `pending` son los links vistos y no visitados: pásale uno a otra llamada para continuar.
Tools MCP
Conecta https://www.easybits.cloud/api/mcp/web con tu API key como Bearer.
web_searchMCPquery, engine?, country?
Busca en Google y devuelve resultados estructurados. 1 consulta.
web_fetchMCPurl, country?, asMarkdown?, onlyMainContent?
Lee una página aunque bloquee bots. 1 consulta.
web_extractMCPsource | datasetId, input, limit?
Extrae registros con esquema (Maps, Mercado Libre, Amazon, Instagram…). Async con jobId; 1 consulta por registro.
web_extract_statusMCPjobId
Estado/registros de un extract. Gratis mientras corre.
web_crawlMCPurl, maxPages?, country?, onlyMainContent?
Rastrea un sitio siguiendo links internos. 1 consulta por página.
Ejemplo de flujo: web_search("ubiquiti u6 mesh precio", country: "mx") → tomar el link de Amazon MX → web_fetch(url, asMarkdown: true) → el agente lee título y precio. Dos consultas.
Formularios
Crea formularios de captura hospedados — servidos en /f/:slug, sin que el usuario final necesite cuenta. Cada envío se guarda, dispara el webhook form.submitted y (si configuraste una) inserta la fila en tu base de datos. Multi-paso por secciones, condicionales y subida de archivos incluidos.
text, email, tel, textarea, select, date, number, checkbox, radio, file, matrix (cuadrícula filas × columnas). Templates: formal, brutalista, institucional, editorial./formsCrea un formulario hospedado standalone. Devuelve la URL pública /f/:slug.
| name | string | Nombre del formulario (requerido) |
| fields | FormField[] | Campos: { name, type, label, required?, placeholder?, options?, showIf?, accept?, section? } |
| theme | string | Template: formal (default) | brutalista | institucional | editorial |
| slug | string | Slug personalizado (opcional; se deriva del nombre) |
| successMessage | string | Mensaje al enviar (opcional) |
/formsLista tus formularios con el conteo de respuestas.
/forms/:formIdActualiza nombre, theme, campos o mensaje de éxito de un formulario.
| name | string | Nuevo nombre (opcional) |
| theme | string | Nuevo template (opcional) |
| fields | FormField[] | Reemplaza los campos (opcional) |
| successMessage | string | Nuevo mensaje al enviar (opcional) |
/forms/:formId/submissionsLista las respuestas de un formulario (más recientes primero).
| limit | number | Query param. Máx 200, default 50. |
type: "file") se guardan privados; la respuesta almacena el fileId. El envío público es POST /forms/:formId/submit (JSON) y la subida POST /forms/:formId/upload (multipart) — ambos sin auth, embebibles en cualquier dominio.Webhooks
Recibe notificaciones POST en tiempo real cuando ocurren eventos. Los payloads se firman con HMAC SHA-256 en el header X-Easybits-Signature. Los webhooks se pausan solos tras 5 fallos de entrega consecutivos.
file.created, file.updated, file.deleted, file.restored, website.created, website.deleted, form.submitted, payment.paid, broadcast.sent/webhooksLista tus webhooks configurados
/webhooksCrea un webhook. El secret solo se devuelve al crearlo — guárdalo.
| url | string | URL HTTPS para recibir las notificaciones POST (requerida) |
| events | string[] | Eventos a los que suscribirse (requerido) |
Máx 10 webhooks por cuenta. La URL debe usar HTTPS.
/webhooks/:webhookIdObtén los detalles del webhook (sin el secret)
/webhooks/:webhookIdActualiza URL, eventos o status del webhook
| url | string | Nueva URL HTTPS |
| events | string[] | Nueva lista de eventos |
| status | string | 'ACTIVE' o 'PAUSED'. Reactivar resetea el contador de fallos. |
/webhooks/:webhookIdBorra un webhook permanentemente
Verificar firmas
Formato del payload
Pagos
Genera links de pago con MercadoPago (Checkout Pro). Conecta tu cuenta en Dashboard → Pagos (pega tu access token). El dinero va directo a tu cuenta de MercadoPago — EasyBits no retiene fondos. Tools del grupo MCP payments.
create_payment_link, list_payment_links. Cuando el pago se aprueba, se dispara el webhook payment.paid.Crear un link de pago
Webhook payment.paid
Email & Broadcasts
Email transaccional, audiencia con tags y newsletters one-shot — todo desde MCP. Los broadcasts agregan un pie de cancelar suscripción automáticamente y saltan a los contactos dados de baja. Tools del grupo MCP email.
send_email, add_contact, list_contacts, create_broadcast, send_broadcast, list_broadcasts. Al terminar un envío se dispara el webhook broadcast.sent.Email transaccional
Audiencia + newsletter
Sitios web
- Crea un sitio — obtienes un
idy una URL tipohttps://my-site.easybits.cloud - Sube archivos con
fileNamepuesto ensites/{websiteId}/path(ej.sites/{id}/index.html) - Haz PUT de los bytes a cada
putUrl, luego pon el status en DONE - Tu sitio está en vivo — el fallback SPA a
index.htmlviene incluido
Ejemplo de deploy
Endpoints
/websitesLista tus sitios web estáticos
/websitesCrea un sitio nuevo
| name | string | Nombre del sitio (requerido) |
/websites/:websiteIdObtén los detalles del sitio
/websites/:websiteIdActualiza el nombre o status del sitio
| name | string | Nuevo nombre |
| status | string | ej. 'DEPLOYED' |
/websites/:websiteIdBorra el sitio y hace soft-delete de todos sus archivos
Documentos
Documentos profesionales generados con IA (reportes, folletos, catálogos, propuestas, CVs) con generación de páginas en paralelo, direcciones de diseño y temas de color semánticos.
/documentsLista todos tus documentos
/documents/:idObtén un documento con todos sus datos de páginas/secciones
/documentsCrea un documento nuevo
| name | string | Nombre del documento (requerido) |
| prompt | string | Descripción para la generación con IA |
| theme | string | Tema: minimal, calido, oceano, noche, bosque, rosa |
| customColors | object | Paleta personalizada: { primary, secondary, accent, surface } |
/documents/:idActualiza la metadata del documento (nombre, tema, colores). Usa las tools de página para cambios de contenido.
| name | string | Nuevo nombre |
| prompt | string | Prompt actualizado |
| theme | string | Nombre del tema |
| customColors | object | Paleta de color personalizada |
/documents/:idBorra un documento
/documents/:id/deployPublica como sitio en vivo en slug.easybits.cloud
/documents/:id/unpublishQuita el sitio en vivo y vuelve a borrador
Gestión de páginas (MCP)
Estas tools están disponibles vía MCP para edición quirúrgica a nivel de página.
get_page_htmlMCPdocumentId, pageId
Obtén el HTML y la metadata de una sola página.
set_page_htmlMCPdocumentId, pageId, html
Actualiza el HTML completo de una página. Preferible a update_document para editar contenido.
get_section_htmlMCPdocumentId, pageId, cssSelector
Obtén el outerHTML de un elemento específico dentro de una página por selector CSS.
set_section_htmlMCPdocumentId, pageId, cssSelector, html
Reemplaza un elemento específico dentro de una página. Permite ediciones quirúrgicas.
add_pageMCPdocumentId, html?, afterPageIndex?, label?
Agrega una página nueva. Opcionalmente pasa el HTML y la posición de inserción.
delete_pageMCPdocumentId, pageId
Elimina una página. No se puede borrar la última que queda.
reorder_pagesMCPdocumentId, pageIds
Reordena todas las páginas. pageIds debe contener cada ID de página exactamente una vez.
get_page_screenshotMCPdocumentId, pageIndex?
Toma un screenshot de una página. Devuelve una imagen PNG (tamaño carta). Úsala para verificar las ediciones visualmente.
Generación con IA (MCP)
generate_documentMCPdocumentId, prompt, skipCover?
Genera todas las páginas con IA vía streaming. Usa skipCover: true para agregar páginas sin regenerar la portada.
refine_document_sectionMCPdocumentId, sectionId, instruction
Cambios quirúrgicos con IA a una página específica. Usa get_page_html para ver el resultado.
regenerate_document_pageMCPdocumentId, sectionId
Rediseña una página por completo manteniendo la misma intención de contenido.
enhance_document_promptMCPname, prompt?, action?
Auto-genera una descripción desde el título o mejora un prompt existente.
get_document_directionsMCPprompt, pageCount?, sourceContent?
Obtén 4 direcciones de diseño (fuentes, colores, mood). Pasa una a generate_document.
clone_documentMCPdocumentId, name?
Duplica un documento con todas sus páginas.
Flujo de trabajo
1. enhance_document_prompt — auto-genera una descripción
2. get_document_directions — obtén 4 direcciones de diseño
3. create_document — crea el documento
4. generate_document — la IA genera todas las páginas
5. get_page_screenshot — verifica las páginas visualmente
6. refine_document_section — ajusta páginas individuales
7. deploy_document — publica en slug.easybits.cloud
Video
Videos editables por escenas que compilan a MP4. Cada escena es una composición HyperFrames: tú das el HTML de la escena (posicionado absoluto, assets como assets/<name>) y un snippet de timeline GSAP opcional contra un tl pausado. Agrega narración por escena → se sintetiza con kokoro (voz em_santa) y se muxea sola; la escena se estira para que la voz quepa. El render corre en un microVM on-demand (decenas de segundos) y el MP4 aterriza en tus archivos, público. Vertical 1080×1920 por default (presets: reel/story/tiktok 9:16, square 1:1, landscape 16:9).
/video-projectsLista tus proyectos de video
/video-projectsCrea un proyecto de video (vacío o con escenas)
| name | string | Nombre del proyecto |
| format | object | Preset de aspecto: { preset: 'reel' | 'story' | 'square' | 'landscape' } |
| theme | string | Fondo: default | dark | light | brand |
| scenes | array | Escenas iniciales opcionales [{ html, timeline?, durationSec?, narration? }] |
/video-projects/:id/scenesAgrega una escena (markup + animación + narración)
| html | string | Markup de la escena (absoluto; assets como assets/<name>) |
| timeline | string | Snippet GSAP contra `tl` (ej. tl.from('#t',{opacity:0,y:40,duration:0.6})) |
| durationSec | number | Duración; si hay narración, se ajusta para que quepa |
| narration | string | Texto de voz en off (kokoro em_santa) |
/video-projects/:id/scenes/:sceneIdEdita una escena. Cambiar narration re-sintetiza la voz en el próximo render.
/video-projects/:id/audioRegistra un asset (imagen/logo) que la caja baja a assets/; referéncialo en el HTML como assets/<name>
| url | string | URL pública del asset |
| name | string | Nombre de archivo, ej. logo.png |
/video-projects/:id/audioMúsica de fondo continua (auto-duckeada bajo la narración). url: null para quitar.
| url | string | URL pública de audio (o null) |
/video-projects/:id/renderCompila, sintetiza la narración pendiente y renderiza a MP4 en el microVM. Síncrono (decenas de segundos).
MCP tools (12)
create_video_projectMCPname?, format?, theme?, scenes?
Crea un proyecto de video doc-style.
list_video_projectsMCPlimit?, offset?, status?
Lista proyectos de video.
get_video_projectMCPprojectId
Proyecto con su lista completa de escenas.
update_video_projectMCPprojectId, name?, theme?, fps?, width?, height?
Actualiza metadata (no toca escenas).
delete_video_projectMCPprojectId
Elimina el proyecto.
add_video_sceneMCPprojectId, html, timeline?, durationSec?, narration?, afterIndex?
Agrega una escena.
set_video_sceneMCPprojectId, sceneId, html?, timeline?, durationSec?, narration?
Edita una escena por id.
delete_video_sceneMCPprojectId, sceneId
Elimina una escena.
reorder_video_scenesMCPprojectId, sceneIds
Reordena todas las escenas.
set_video_musicMCPprojectId, url, name?
Música de fondo (o url:null para quitar).
attach_video_assetMCPprojectId, url, name?, type?
Registra imagen/logo como asset.
render_video_projectMCPprojectId
Compila + renderiza a MP4 con narración kokoro.
Agentes & Sandboxes
MicroVMs Firecracker para correr agentes y código aislado. Crea sandboxes, ejecuta comandos, expón puertos, y despliega agentes persistentes — todo desde el SDK, REST API o herramientas MCP.
Templates
Cada sandbox se crea desde un template. Estos son los disponibles:
| Template | Tipo | Descripción |
|---|---|---|
| code-interpreter | sandbox | Python con kernel Jupyter persistente. Variables, imports y gráficas sobreviven entre celdas |
| python / node / bun | sandbox | Runtimes base. Cada sandbox_run_code ejecuta un proceso fresco |
| ubuntu | sandbox | Linux completo. Ideal para instalar paquetes, compilar, o correr servidores |
| rust-ghosty | agente | Ghosty: cerebro CodeWhale/Rust DeepSeek-first con canales web SSE y WhatsApp |
| claude-code | agente | Claude Agent SDK loop. Modelo Sonnet 4.6, billing por token |
| computer-ghosty | agente | Computer-use con escritorio Linux XFCE + terminal noVNC público |
| ghostyclaw / openclaw | agente | Daemons always-on para WhatsApp, Slack, Telegram |
Flujo básico: sandbox efímero
Crea un sandbox, ejecuta código, expón un puerto, destrúyelo. Ideal para ejecución aislada.
Endpoints REST
Todo el SDK corre sobre estos endpoints. Base: https://www.easybits.cloud/api/v2. Auth por header Authorization: Bearer eb_sk_live_... — ver Autenticación.
| Método & ruta | Body | Qué hace |
|---|---|---|
| GET /sandboxes | — | Lista tus sandboxes vivas |
| POST /sandboxes | template*, timeoutSeconds, name, size, metadata, persistent, suspendOnIdle, hardTtlSeconds | Crea una microVM |
| GET /sandboxes/:id | — | Estado de una caja |
| DELETE /sandboxes/:id | — | La destruye |
| POST /sandboxes/:id/exec | command*, cwd, env, timeoutSeconds | Corre un comando de shell |
| POST /sandboxes/:id/run-code | code*, lang (python|node|bash), timeoutSeconds | Ejecuta código en proceso fresco |
| POST /sandboxes/:id/run-cell | code*, timeoutSeconds | Celda en el kernel persistente |
| POST /sandboxes/:id/kernel-restart | — | Reinicia el kernel Jupyter |
| POST /sandboxes/:id/expose | port* | URL pública HTTPS del puerto |
| POST /sandboxes/:id/expose-raw | port*, protocol* (tcp|udp) | Forward L4 crudo |
| POST /sandboxes/:id/unexpose-raw | port*, protocol* | Cierra el forward L4 |
| POST /sandboxes/:id/suspend | — | Duerme la caja (snapshot) |
| POST /sandboxes/:id/resume | — | La despierta |
| POST /sandboxes/:id/extend | extendSeconds | Alarga el TTL |
| POST /sandboxes/:id/snapshot | name | Congela el disco en una imagen |
| POST /sandboxes/:id/fork | count, name, metadata, timeoutSeconds | Clona N hijos copy-on-write |
| POST /sandboxes/:id/logs | — | Lee logs |
| POST /sandboxes/:id/apply-patch | — | Aplica un patch de archivos |
| POST /sandboxes/:id/ssh-enable | — | Habilita SSH (ver arriba) |
| POST /sandboxes/:id/domain-add | — | Dominio propio + HTTPS |
| GET /sandboxes/:id/files/read | ?path= | Lee un archivo |
| GET /sandboxes/:id/files/list | ?path= | Lista un directorio |
| POST /sandboxes/:id/files/write | path*, content* | Escribe un archivo |
| POST /sandboxes/:id/files/delete | path* | Borra |
| POST /sandboxes/:id/files/move | from*, to* | Mueve o renombra |
| POST /sandboxes/:id/files/mkdir | path* | Crea directorio |
* = requerido. Un body inválido devuelve 400 con issues[] de Zod. Crear sandboxes tiene rate limit propio — ver Errores & Límites.
Snapshot & fork (clonado copy-on-write)
Congela el estado de una caja viva en una imagen nombrada (snapshot) y arranca N hijos desde ella (fork). Cada hijo es una caja independiente con su propia IP. Patrón estrella: prepara el entorno una vez (deps instaladas, proyecto listo), snapshotea, y bifurca en paralelo para probar N variantes — sin repetir el setup en cada una.
Exponer un puerto (URL pública)
Arranca un servidor dentro del sandbox y obtén una URL HTTPS pública al instante.
Tu servicio debe bindear a 0.0.0.0, no a 127.0.0.1: el proxy dialea la IP del guest, así que un bind a loopback es inalcanzable por diseño (igual que en Docker, Fly o Cloud Run). Si al exponer el puerto ya hay algo escuchando sólo en loopback, la respuesta trae un campo warning y la URL responderá 502 hasta que lo cambies.
Puertos raw (TCP/UDP)
sandbox_expose_port ya sirve capa 7 con TLS: HTTP y WebSocket — la misma URL responde https:// y wss://, sin túnel ni puerto raw. Lo que no hace es capa 4 cruda: los puertos 22, 23, 25, 445 y 3389 se rechazan con 400. Para un servicio que no habla HTTP usa el forward de capa 4.
- El
hostPortsale de un pool (49000-49999): es distinto por caja y no es igual al puerto de adentro — así cada caja tiene su propio 22. - No es estable: se libera al destruir la caja y se re-asigna. Vuelve a leerlo; no lo guardes ni lo pongas fijo en tu UI.
- Gateado por el template: un 403 significa "este template no tiene ese puerto". Es definitivo, no reintentes.
- Cerrarlo:
sandbox_unexpose_raw_port.
SSH a una caja
Una sola llamada inyecta tu llave, reinicia el sshd de la caja y abre el 22. Te devuelve el comando listo para pegar.
La llave pública sale del CLI. No la escribas a mano ni elijas una de ~/.ssh: inyectar una pública que no corresponde a la privada con la que luego conectas da Permission denied con todo lo demás correcto, y es el error más común de este flujo.
- El sshd de la caja es fail-closed: sin llave no arranca. Por eso la llave va primero — una caja sin llave no tiene superficie SSH ni siquiera cerrada.
- Acceso solo por llave, como
root. Varias llaves: una por elemento del array. - La host key vive en
/app/ssh/: el fingerprint sobrevive reinicios y resume, así que no verás el warning de MITM en cada boot. sandbox_ssh_disablecierra el puerto pero no revoca: para eso quita la llave de/app/secrets.env.- Solo en templates que declaren el 22 (hoy
ghosty-studio).
SSH por túnel — recomendado
El comando de arriba usa un puerto alto del anfitrión, y un puerto alto no atraviesa la red de una oficina ni una VPN corporativa. Eso te llega como “no me conecta” desde una red que no puedes reproducir. El túnel entra por el mismo 443 de siempre: si el usuario puede abrir una página web, entra a su caja.
En ~/.ssh/config:
Y ya:
- Sigue haciendo falta
ssh-enableuna vez, para inyectar la llave: el sshd de la caja es fail-closed. Pásale la salida deeasybits ssh-key— es la misma que usassh-proxyal conectar, así que no pueden desfasarse. La privada nunca sale de tu máquina. - El nombre de la caja sirve como host. No es único ni secreto: si dos cajas lo comparten el proxy falla en vez de elegir, porque entrar a la equivocada es peor que no entrar. Que sea público da igual — la sesión se autentica con tu llave y el ticket, nunca con el nombre.
- El túnel no autentica. Mueve bytes opacos; la sesión SSH se autentica de punta a punta entre tu
sshy el sshd de la caja. Un fallo en el túnel no le da acceso a nadie. - El CLI pide un ticket firmado de vida corta (
sb.sshTicket()) y abre el WebSocket. Normalmente no lo llamas tú. - Ticket vencido, firma alterada o caja ajena: 403/404 en el borde, sin llegar al anfitrión.
Dominio personalizado (custom domain + HTTPS automático)
Sirve un puerto del sandbox bajo tu propio dominio con certificado TLS emitido automáticamente — sin egress fees, sin configurar nada de TLS. Funciona con subdominios (app.cliente.com) y dominios raíz (cliente.com).
sandbox_domain_add→ te devuelve endnsel registro EXACTO a crear.- Crea ese registro en tu DNS: subdominio → CNAME a
cname.sandboxes.easybits.cloud; raíz/apex → A a la IP del edge (apex no admite CNAME). sandbox_domain_verify→ confirma que ya resuelve y sirve con TLS. El cert se emite solo en el primer acceso.
Nota: crea el registro en tu DNS autoritativo. Si tu registrador delega los nameservers a otro proveedor (ej. Google Cloud DNS, Route53), edítalo ahí — no en el panel del registrador.
Kernel persistente (code-interpreter)
El template code-interpreter mantiene un kernel Jupyter con estado entre celdas. Variables, imports y gráficas (matplotlib) sobreviven.
Agentes persistentes (agent_create)
Crea agentes de larga duración con un endpoint HTTP público. Ideal para chatbots embebidos, asistentes en WhatsApp, o dashboards.
Agent Run (one-shot)
Dispara un agente Claude para una tarea, espera el resultado, y destruye el sandbox. Ideal para CI/CD, procesamiento por lotes, o tareas puntuales.
¿Se despierta sola?
Depende de quién le hable, y es la pregunta que más se repite:
- Agente de flota (los que corren tus conversaciones): sí. Si su caja está suspendida, el turno la reanuda antes de correr, y si el snapshot se perdió arranca una VM limpia con la memoria restaurada. No hace falta que el usuario vuelva a escribir ni que llames a
sandbox_resume. - Sandbox tuyo con tu propio servidor: no. Ahí no hay turno de agente, así que nada la reanuda por ti. Llama a
sandbox_resumedesde tu backend antes de usarla.
⚠️ Una conexión no despierta una caja: abrir un WebSocket contra una caja suspendida no la reanuda, negocia contra una máquina apagada. El error aparece en el cliente mientras tu servidor cree que todo fue bien. Reanuda primero, conecta después.
Bootstrap al reanudar
Una caja restaurada de un snapshot revive sin boot: no vuelve a correr systemd, ni el entrypoint, ni .bashrc. Una caja que durmió tres días despierta con el mundo de hace tres días, y nada lo señala. El bootstrap es la receta que el host corre en cada despertar, venga de donde venga (una petición al proxy de un puerto, un mensaje al agente, el dominio público) — no sólo cuando llamas a sandbox_resume.
- No es un campo de
POST /sandboxes: es una segunda llamada sobre la caja ya creada. - Hazlo idempotente — corre en CADA despertar:
checkout -B, no-b;ln -sfn, noln -s. - Variables disponibles:
EB_RESUME=1yEB_SANDBOX_ID. El cwd es/data/work. - Un script que falla nunca deja la caja inalcanzable: el resultado queda anotado (
eb_boot_exit,eb_boot_err). Script vacío = apagarlo. - ⚠️ Nunca metas una credencial en el script: la receta viaja en el metadata de la caja y aparece en los listados. Usa
$secret:desde una tool de git.
Git: que el trabajo del agente sobreviva a la caja
Sin una forma de publicar, lo que el agente escribió muere con la caja. Siete herramientas cierran el ciclo: clonar el repo, trabajar, publicar. Es también la pieza que bootstrapea el repo de un agente (código + skills) en cada arranque.
.git/config ni aparece en la línea de comando, así que un ps desde adentro no lo ve. Un URL con credenciales embebidas (https://user:token@…) se rechaza con 422 en vez de aceptarse en silencio, precisamente porque git SÍ lo persistiría.sandbox_git_statusdevuelve datos, no texto:{ branch, upstream, ahead, behind, clean, staged[], modified[], untracked[], conflicted[] }. Sale deporcelain=v2, así que no cambia entre versiones de git ni con el idioma del sistema.sandbox_git_commitsin cambios devuelve{ nothingToCommit: true }como éxito. Un error ahí invita al agente a reintentar, y reintentar no cambia nada: es un bucle.sandbox_git_checkoutconcreate: trueusa-B, idempotente — pensado para correrse en cada arranque sin fallar con "branch already exists".sandbox_git_pushconforceusa--force-with-lease: si alguien más empujó a esa rama, falla en vez de borrarle el trabajo.- La identidad del commit va por llamada (
authorName/authorEmail, defaultEasyBits Agent), sin dejar ungit configescrito en el repo del cliente. - Para repos privados en
launch_app, el mismo mecanismo:launch_app({ repo, repoToken: "$secret:GITHUB_TOKEN" }). El token no entra al runspec ni al tarball del release.
Herramientas MCP del grupo sandbox
sandbox_createMCPtemplate, timeoutSeconds
Crear un sandbox nuevo
sandbox_listMCP—
Listar sandboxes activos
sandbox_statusMCPsandboxId
Estado del sandbox (running/stopped/error)
sandbox_destroyMCPsandboxId
Destruir y liberar recursos
sandbox_extendMCPsandboxId, extendSeconds
Extender TTL del sandbox
sandbox_suspendMCPsandboxId
Snapshot a disco y liberar CPU (pausa el TTL)
sandbox_resumeMCPsandboxId
Restaurar desde snapshot (restaura el TTL restante)
sandbox_execMCPsandboxId, command
Ejecutar comando (sync, 60s por defecto, tope 600s)
sandbox_exec_backgroundMCPsandboxId, command
Ejecutar comando en background
sandbox_exec_listMCPsandboxId
Listar procesos en background (recupera un execId perdido)
sandbox_exec_statusMCPsandboxId, execId
Consultar estado de ejecución background
sandbox_exec_killMCPsandboxId, execId
Matar una ejecución background (lo que falta cuando algo se cuelga)
sandbox_run_codeMCPsandboxId, code, lang
Ejecutar Python/Node/Bash inline
sandbox_run_cellMCPsandboxId, code
Ejecutar celda en kernel Jupyter persistente
sandbox_set_bootstrapMCPsandboxId, script, mode?, timeoutSeconds?
Receta que el host corre en CADA despertar (idempotente)
sandbox_git_cloneMCPsandboxId, repo, dir, branch?, depth?, commit?, token?
Clonar un repo dentro de la caja (token por llamada)
sandbox_git_statusMCPsandboxId, dir
Estado como datos: branch, ahead, behind, clean, archivos
sandbox_git_commitMCPsandboxId, dir, message, addAll?, paths?
Commit (sin cambios → nothingToCommit, es éxito)
sandbox_git_pushMCPsandboxId, dir, branch?, setUpstream?, token?
Publicar (force usa --force-with-lease)
sandbox_git_pullMCPsandboxId, dir, rebase?, token?
Traer cambios del remoto
sandbox_git_checkoutMCPsandboxId, dir, branch, create?, from?
Cambiar de rama (create usa -B, idempotente)
sandbox_git_logMCPsandboxId, dir, limit?, cursor?
Historial paginado
sandbox_files_writeMCPsandboxId, path, content
Escribir archivo en el sandbox
sandbox_files_readMCPsandboxId, path
Leer archivo del sandbox
sandbox_files_listMCPsandboxId, path
Listar directorio
sandbox_files_editMCPsandboxId, path, oldString, newString
Edición quirúrgica in-place (sin escaping de shell)
sandbox_logsMCPsandboxId, unit?, lines?, since?, grep?
Logs journald nativos del daemon
sandbox_runtimeMCPsandboxId, action, unit?, buildCommand?
systemd status/restart/rebuild del daemon
sandbox_apply_patchMCPsandboxId, edits[], rebuild?, restart?
Hotfix atómico: edita → rebuild → restart
sandbox_adminMCPsandboxId, path, method?, body?
Pasarela al admin API interno (:8787) de una máquina permanente
sandbox_expose_portMCPsandboxId, port
Exponer puerto como URL pública HTTPS (solo HTTP)
sandbox_expose_raw_portMCPsandboxId, port, protocol
Forward TCP/UDP crudo; devuelve endpoint host:hostPort
sandbox_unexpose_raw_portMCPsandboxId, port, protocol
Cerrar el forward TCP/UDP
sandbox_ssh_enableMCPsandboxId, publicKeys[]
SSH a la caja: inyecta llave, abre el 22, devuelve el comando
sandbox_ssh_disableMCPsandboxId
Cerrar el puerto SSH (no revoca la llave)
sandbox_domain_addMCPsandboxId, domain, port
Atar dominio propio (devuelve el registro DNS: CNAME o A)
sandbox_domain_removeMCPsandboxId, domain
Quitar dominio personalizado
sandbox_domain_listMCPsandboxId
Listar dominios del sandbox
sandbox_domain_verifyMCPdomain
Confirmar DNS + cert TLS del dominio
agent_createMCPtemplate
Crear agente persistente (endpoint HTTP)
agent_listMCP—
Listar agentes persistentes
agent_messageMCPagentId, content
Enviar mensaje a un agente
fleet_agent_listMCP—
Grupo fleet · listar los agentes de la flota (WhatsApp/web/Teams)
fleet_agent_capabilitiesMCPfleetAgentId
Grupo fleet · config actual de un agente (= GET /capabilities)
fleet_agent_configureMCPfleetAgentId, action, params?
Grupo fleet · aplicar una acción de /capabilities
agent_runMCPprompt, model?
Agente Claude one-shot (async)
agent_run_statusMCPjobId
Consultar estado de agent_run
templates_listMCPtier?
Listar templates disponibles
Ghosty Lite
Un agente de verdad en su propia microVM: fork ligero de goose escrito en Rust, que habla ACP nativo. Tiene shell, edita archivos, y su disco (/data) sobrevive a que se duerma. Lo que lo distingue: corre con TU llave de EasyBits como cerebro — no traes llave de OpenAI ni de Anthropic, y el consumo se descuenta de tus tokens.
eb_sk_live_… es la credencial del modelo (vía el proxy LLM, modelo deepseek-v4-pro). Necesita scope WRITE y saldo de tokens: míralo en GET /api/v2/llm/balance.De cero a conectado
Cuatro llamadas. Cada quien con su propia llave: el agente es suyo y su consumo se descuenta de su cuenta.
1. Crear el agente
Primero tu llave (ésta la editas: pega la tuya, de Dashboard → Developer):
Y ahora sí, el comando tal cual:
2. Esperar a que esté listo y tomar la URL
Copia de la respuesta anterior el agentId y el embedToken, y guárdalos en variables:
Y consultas su estado:
3. Conectar tu cliente
La URL del paso 2 más el token del paso 1. Sin token, o con uno equivocado, responde 401.
4. Ver tu saldo
wss sale del paso 2, no del 1 — en el 1 todavía viene una provisional sandbox://…. Y tu llave necesita scope WRITE y saldo de tokens: sin saldo el agente responde 402 insufficient_quota y parece mudo.Tus propias tools (MCP)
El agente monta servidores MCP propios al abrir su sesión: los declaras al crearlo, en mcpServers. La caja ya trae node, npx y python3, así que un MCP tuyo corre sin instalar nada en la imagen.
Dos formas, las dos del protocolo ACP:
- stdio —
name+command+args+env. El proceso vive dentro de la microVM. - http / sse —
type+name+url+headers. Un servidor remoto, el tuyo o el de EasyBits.
env y headers aceptan objeto { "CLAVE": "valor" } o la lista [{ "name": …, "value": … }] del protocolo; da igual cuál escribas. Un valor $secret:NOMBRE se resuelve contra tu vault al arrancar la caja — dentro del valor, así que "Bearer $secret:EASYBITS_API_KEY" queda como esperas: la credencial no queda escrita en el comando ni se guarda dos veces, y rotarla basta para que el próximo arranque tome la nueva.
mcpServers vuelven con ella: quedan guardados en el agente, cifrados.La API en detalle
Lo de arriba es todo lo que hace falta. Esto es la referencia de los mismos endpoints, y dos cosas que conviene entender: running significa que EasyBits ya hizo el initialize + session/new y guardó la sesión (~6 s) — antes de eso no hay a quién mandarle el mensaje. Y el env vacío es lo normal: sólo lo llenas para cambiar de cerebro o para elegir tú el token ({ "ACP_AGENT_TOKEN": "el-tuyo" }, y entonces el embedToken deja de servir).
/api/v2/agentsCrea el agente. Devuelve agentId y embedToken; su agentUrl es provisional todavía.
| template | string | "ghosty-lite" (requerido) |
| name | string | Cómo lo verás en tu lista de agentes |
| env | object | Vacío para el cerebro medido con tu llave. Ver Otro cerebro. |
| mcpServers | array | Tus propias tools MCP (stdio o http/sse). Ver #ghosty-lite-mcp. |
/api/v2/agents/:agentIdEstado del agente. Cuando status es running, agentUrl trae la URL WebSocket estable.
Hablarle por HTTP
/api/v2/agents/:agentId/messageUn turno de conversación. Devuelve SSE. Sirve la eb_sk del dueño o el embedToken desde el navegador.
| content | string | El mensaje del usuario (requerido) |
| sessionId | string | Hilo de conversación (default 'default'). Un id por visitante en embeds multi-usuario. |
Eso de arriba es lo que RECIBES, no lo que mandas. El evento usage llega justo antes del done, cuando el agente lo reporta, y son totales de la SESIÓN, no del turno.
Desde tu editor o cliente ACP
Zed, JetBrains, VS Code, Ghosty Teams o cualquier cliente ACP se conectan a la URL estable del agente. Lleva el agentId, no la máquina: si el host recicla la caja, la URL sigue siendo la misma. El token siempre va en la URL (?token=) porque es lo único que todo cliente sabe pasar — un WebSocket de navegador no puede poner cabeceras. Si el tuyo puede, Authorization: Bearer también vale.
El puerto ya está expuesto; no hace falta /expose. El cwd de la sesión es /data/work — si tu cliente manda otro que no exista en la caja, se degrada a ése.
Duerme, despierta y revive
Tras 2 h sin actividad se duerme (no muere: vive hasta 30 días). El siguiente mensaje la despierta en ~1 s con su disco y su conversación intactos, y la URL nunca cambia. Ojo si estás conectado por WebSocket: al dormirse se te cae el socket y hay que reconectar. POST /api/v2/agents/:id/revive es sólo para cuando el host recicló la caja tras días sin uso — tarda un boot (~10-60 s) y pierde el disco.
Otro cerebro (BYOK)
El default es el cerebro medido, pero el env manda. Si traes tu propia llave, el gasto va contra ese proveedor y EasyBits no lo cuenta.
| Cerebro | env | Consumo |
|---|---|---|
| easybits | {} (default) | Tus tokens de EasyBits |
| anthropic | GHOSTY_PROVIDER, GHOSTY_MODEL, ANTHROPIC_API_KEY | Tu cuenta de Anthropic. API key normal — un token OAuth (sk-ant-oat…) no sirve: este proveedor autentica con x-api-key. |
| custom_deepseek | DEEPSEEK_API_KEY | Tu cuenta de DeepSeek |
| openai · openrouter · google · ollama | GHOSTY_PROVIDER + su *_API_KEY | Tu proveedor |
ghosty-gc.Preguntas que siempre salen
¿De dónde sale el token de la URL wss?
Es el embedToken que te devuelve el paso 1. No se genera en la caja ni cambia con el tiempo: vive con el agente.
La trampa: si al crear pasaste tu propio ACP_AGENT_TOKEN en el env, el token es ése y el embedToken deja de servir — aunque siga apareciendo en la respuesta. Es uno o el otro, nunca los dos.
¿Hace falta el ?token= o puedo omitirlo?
Hace falta. Sin él (o con uno equivocado) el agente responde 401, tanto en el WebSocket como por HTTP. Si tu cliente no puede poner query params, vale igual como Authorization: Bearer <token>.
Mi cliente pide un ACP_SECRET, ¿qué pongo?
El mismo token del agente. Hay un secreto interno distinto que la caja genera en cada arranque para hablar consigo misma, pero nunca sale de la microVM y desde fuera no sirve para nada.
¿Y el cwd?
/data/work. Es el único directorio que sobrevive a que la caja se duerma. Si tu cliente manda otro que no exista dentro (típico: /root o la ruta de tu Mac), el agente lo degrada a /data/work sin avisar.
¿Puedo usar mi llave de DeepSeek en vez del cerebro medido?
Sí: env: { "DEEPSEEK_API_KEY": "sk-..." } al crear. Gana sobre el medido y el gasto va contra tu cuenta de DeepSeek, no contra tus tokens de EasyBits.
¿Y el OAuth de Claude (mi suscripción Max)?
Hoy no. El proveedor anthropic autentica con x-api-key, así que un sk-ant-oat… no entra; una API key normal de Anthropic sí. Estamos preparando "trae tu suscripción" como opción aparte.
Creé el agente y mi cliente no conecta
Casi siempre es una de tres: estás usando la URL del paso 1 (provisional) en vez de la del paso 2; tu llave no tiene scope WRITE; o no te quedan tokens (el agente responde 402 y parece mudo). Revisa el saldo con GET /api/v2/llm/balance.
¿Se me borra si no lo uso?
No. Tras 2 h sin actividad se duerme, y el siguiente mensaje lo despierta con su disco y su conversación intactos. La URL nunca cambia. Si pasan días y el host recicló la caja, POST /api/v2/agents/:id/revive lo levanta en la misma URL (eso sí pierde el disco).
Herramientas MCP
agent_createMCPtemplate: "ghosty-lite", env
Crear el agente (mismo flujo que el POST)
agent_messageMCPagentId, content
Un turno; devuelve { content, tokens, usage? }
agent_listMCP—
Listar tus agentes
agent_destroyMCPagentId
Destruir la caja
Flota
Tu flota es un grupo de agentes Ghosty que atienden tus grupos de WhatsApp 24/7. Respondes a tus clientes al instante, sin contratar a nadie y sin dejar a nadie esperando. Conectas tu WhatsApp una vez y eliges en qué grupos contesta.
eb.fleet.create({ engine, name, systemPrompt }), eb.fleet.setModel, setAgentPrompt, setToolGroup…) o la REST /api/v2/fleet-agents. Motores: Claude, DeepSeek, Codex. Así es como Formmy configura sus agentes en tu flota.Cómo conectar (WhatsApp personal)
- Entra a /dash/flota y crea un agente.
- Vincúlalo a tu WhatsApp: escanea el código QR (o usa el código con tu número) desde WhatsApp → Dispositivos vinculados → Vincular dispositivo.
- Prende los grupos que quieras que atienda con los toggles. El agente solo responde en los grupos activos — los demás los ignora (anti-spam).
WhatsApp Business (WABA)
Conecta tu WhatsApp Business API oficial y tu flota atiende desde tu número oficial, sin el riesgo de bloqueo de la vinculación personal. WABA atiende conversaciones 1:1 con tus clientes (no grupos) — ideal para soporte y ventas directas. Cada número tiene su propia identidad (nombre y persona), su propio Inbox y su propio estado de respuesta.
- En /dash/flota, sobre tu agente, pulsa Conectar WhatsApp Business. Se abre el wizard de Meta (Embedded Signup) en un popup.
- Sigue los pasos de Meta para vincular tu cuenta de WhatsApp Business. Al terminar, el número queda asociado a ese agente.
- Un número recién conectado arranca apagado. Elige su estado de respuesta en Conversaciones para que empiece a atender.
Inbox y estados de respuesta (por número)
Cada número WABA tiene un Inbox (botón Conversaciones): ves quién le escribe al agente, su último mensaje, y eliges con granularidad a quién responde. El estado se aplica al instante. Hay 3 estados:
- Apagado — no responde a nadie en ese número.
- Activo — responde a todos, excepto las conversaciones que pauses (cuando quieres atenderlas tú). Útil para soporte abierto.
- Solo a… — responde solo a las conversaciones que actives (lista blanca). Útil cuando estrenas el bot con un grupo reducido antes de abrirlo a todos.
En el Inbox buscas por nombre o número, ves quién está En pausa y, con un botón, pausas o reactivas el agente en cada conversación. Cualquier cambio refresca el Inbox de inmediato.
Cómo funciona: cajas
La capacidad de tu flota se compra en cajas. Cada caja corre 4 agentes Ghosty a la vez y cuesta $299 MXN/mes como suscripción mensual. ¿Necesitas atender más conversaciones al mismo tiempo? Agrega más cajas — cada caja suma 4 agentes a tu flota.
Agentes en tu app
La Flota no es sólo WhatsApp. El mismo agente atiende tu aplicación por HTTP: le mandas un mensaje, te devuelve la respuesta. No hay nada que registrar — ni grupo, ni número, ni alta previa. Y lo que configuras aquí (prompt, tools, tus propios MCP) es lo mismo que ve el agente en cualquier otro canal.
Si lo que quieres es un chat dentro de tu app, no uses este token: emite una credencial con alcance y, para el navegador, un token de sesión. Así lo que viaja al cliente sólo puede mandar mensajes.
1. Hablarle
Dos formas, mismo motor: /message devuelve la respuesta completa en JSON; /message-stream la manda por SSE (chunk conforme se escribe, y un done final cuyo value es la respuesta autoritativa — arma el mensaje con ése, no concatenando los chunks). También puede llegar capacity: tu flota está llena en ese instante. No es un fallo del turno — reintenta pasado su retryAfter.
El groupId es opaco: identifica una conversación y lo eliges tú. Un web-<uuid> por usuario, o el id de tu propia tabla de chats. Mismo groupId = misma memoria; uno nuevo = conversación nueva.
configGroupId. Es la unidad de configuración (prompt, tools, MCPs); el groupId sólo identifica la conversación. Si lo omites, cada conversación busca una config con su propio id, no la encuentra, y el agente arranca sin tus conectores — se ve idéntico a un MCP roto ("no tengo esa herramienta"). Usa un valor estable para toda tu app ("mi-app") y configura ESE.Los MCP se montan al crear la sesión, no en cada turno: para comprobar un cambio de configuración, prueba con un
groupId nuevo.2. Credenciales con alcance
El token del agente sirve para todo: mandar mensajes, cambiar el prompt, leer secretos y borrar el agente. Eso está bien para tu backend, pero no para repartirlo entre integraciones — y mucho menos para el navegador. Emite credenciales que hagan una sola cosa.
| Scope | Puede | No puede |
|---|---|---|
| MESSAGE | Mandar turnos (/message, /message-stream). | Nada de configuración. |
| MANAGE | Todo lo anterior + leer y ajustar config: prompt, modelo, effort, canales, capacidades. | Secretos, MCPs, skills, motor, borrar. |
| ADMIN | Todo, incluidos set-secret, add-mcp, set-engine y borrar el agente. | — |
flt_sk_ es secreta: cualquier scope, sólo por header, y se rechaza si la mandas por query string (ahí acabaría en logs de acceso y en el Referer). flt_pk_ es publishable: sólo MESSAGE, admitida en el navegador y acotada por allowedOrigins.El valor completo se muestra una sola vez, al crearla. Después sólo verás su prefijo. También puedes emitirlas y revocarlas desde /dash/flota.
cfgId importa en el token de sesión. Un token que lo lleva ignora el configGroupId que mande el cliente. Sin eso, una sesión emitida para un cliente podría pedir la configuración de otro simplemente cambiando un campo del body.3. Embeberlo
4. Configurarlo
Todo pasa por /api/v2/fleet-agents/:id/capabilities. El dashboard de EasyBits es sólo un cliente de este endpoint: lo que puedes hacer con la UI, lo puedes hacer por API. GET devuelve el catálogo y el estado actual; POST aplica una mutación con action.
| action (REST) | SDK | Qué hace | Alcance | Scope mín. |
|---|---|---|---|---|
| set-agent-prompt | setAgentPrompt | El prompt base: quién es el agente. | Todo el agente | MANAGE |
| set-model | setModel | Modelo del motor. | Todo el agente | MANAGE |
| set-effort | setEffort | Cuánto piensa: low · medium · high · xhigh. | Todo el agente | MANAGE |
| add-mcp | addMcp | Conecta tu API como MCP: url (Streamable-HTTP, el secret viaja como Authorization: Bearer) o pkg (npm, stdio, como env var). Sólo lo registra: enciéndelo con set-cap-level. | Todo el agente | ADMIN |
| remove-mcp | removeMcp | Lo quita del catálogo. | Todo el agente | ADMIN |
| recycle-box | recycleBox | Recicla las cajas del agente: el siguiente turno arranca una nueva con env fresco (motor, modelo, llave del motor). Respalda las conversaciones antes; no corta turnos en vuelo. | Todo el agente | ADMIN |
| set-engine | setEngine | Cambia el motor (Claude, DeepSeek, Codex…). Recicla las cajas solo. | Todo el agente | ADMIN |
| set-name | setName | Nombre del agente. | Todo el agente | MANAGE |
| toggle-own-number | toggleOwnNumber | Número dedicado: sin prefijo Nombre: en WhatsApp. | Todo el agente | MANAGE |
| add-skill · toggle-skill · delete-skill | addSkill · toggleSkill · deleteSkill | Skills (SKILL.md + scripts subidos a Files como fileIds). | Todo el agente | MANAGE · ADMIN para add-skill · delete-skill |
| connect-teams | connectTeams | Marca el canal Teams como conectado. | Todo el agente | MANAGE |
| toggle-asset | toggleAsset | Archivo del owner adjunto como contexto del canal. | Por canal | MANAGE |
| set-db-allow | setDbAllow | Namespaces de DB que el agente puede tocar ([] = todas). | Por canal | MANAGE |
| set-secret | setSecret | Guarda la credencial que usa un MCP (cifrada, no se vuelve a leer). | Todo el agente | ADMIN |
| set-prompt | setGroupPrompt | Prompt que se suma al base, sólo en este canal. | Por canal | MANAGE |
| set-toolgroup | setToolGroup | Qué tools de EasyBits ve: buckets (imágenes, documentos, investigación…). | Por canal | MANAGE |
| set-cap-level | setCapLevel | Nivel de una capacidad: off · read · write. | Por canal | MANAGE |
| set-tool-deny | setToolDeny | Prohíbe una tool concreta. | Por canal | MANAGE |
| toggle-builtin | toggleBuiltin | Prende/apaga un conector incluido. | Por canal | MANAGE |
Las acciones "por canal" llevan groupId — y ahí va tu configGroupId, el mismo que mandas al hablarle.
Las marcadas ADMIN pueden sacar una credencial del vault, meter un servidor ajeno en el turno o destruir trabajo — por eso una credencial MANAGE no las alcanza.
Las mismas acciones existen por MCP: fleet_agent_list → fleet_agent_capabilities → fleet_agent_configure { fleetAgentId, action, params }, autenticadas con tu API key (no con el token del agente), en el grupo fleet del conector (?tools=fleet). Para borrar un agente: POST /api/v2/fleet-agents/:id/delete.
Tres capas de prompt
Se suman, nunca se pisan. De más estable a más volátil:
- Base del agente —
set-agent-prompt. Quién es, en todos los canales. - Del canal —
set-promptcongroupId. Cómo se comporta en tu app. - Del turno —
appendSystemPrompten el propio mensaje. Quién pregunta, qué plan tiene, qué está viendo. Sin escribir nada en la config.
agent_* que verás en Agentes & Sandboxes son de sandboxes, otra cosa. Un agente todavía no puede configurar a otro agente.Los métodos del SDK viven en SDK (
eb.fleet.*); para crear el agente y sacar su token, Flota.Sandboxes permanentes
Un sandbox efímero se auto-destruye al TTL. Una sandbox permanente corre 24/7 y se cobra flat en MXN/mes como item de suscripción encima de tu plan. Mismo recurso, mismo sandboxId — "permanente" es solo un flag + cobro. La operas igual que cualquier sandbox (exec, archivos, expose_port, dominios).
hosting. Agrega --tools hosting para habilitarlas. Requiere plan de pago (Mega/Tera) — el plan es el gate de acceso.Catálogo de tiers
| Tier | vCPU / RAM / NVMe | Shared | Reserved |
|---|---|---|---|
| nano | 1 / 256MB / 1GB | $49 | — |
| micro | 1 / 1GB / 2GB | $99 | — |
| estandar | 2 / 2GB / 16GB | $449 | — |
| focus | 4 / 8GB / 32GB | $690 | $1,725 |
| performance | 8 / 16GB / 64GB | $1,290 | $3,225 |
| performance-4x | 16 / 32GB / 128GB | $4,980 | $12,450 |
Precios MXN/mes, NVMe, sin cobro de tráfico. Disco add-on: +100GB NVMe = $99/mes (apilable). CPU reserved (piso garantizado por cgroup) solo desde focus. Para correr una app 24/7 (migrar desde Fly/Render) empieza en micro; nano (256MB) da para un binario estático o un side-project, no para un build de Node.
Tu app en producción en una sola llamada
launch_app es el fly launch de EasyBits: provisiona la máquina, mete el código, lo buildea, lo arranca, te da una URL HTTPS pública, publica el release de recuperación y, si le pasas un dominio, lo conecta con TLS. Te devuelve { url, releaseId, domain.dns }.
El código puede venir de tres lados: repo (git clone), archiveUrl (un .tar.gz o .zip subido desde tu compu, por si aún no tienes repo), o sandboxId (ya escribiste la app dentro de una caja). Si el build falla, la máquina que creó se libera sola — no te quedas pagando una caja rota.
Tiempos medidos con una app React Router v7 real (204 MB de node_modules, release de 49.5 MB) en tier micro: provisionar la caja 3.7 s · npm ci + build en la caja 6.9 s · publicar el release 11.3 s · redeploy a una caja limpia 12.0 s. Con prebuilt: true el deploy no ejecuta build: baja, extrae y arranca. ⚠️ Buildea dentro de la caja, no en tu Mac: un node_modules con módulos nativos compilado en macOS revienta en Linux.
Configurar la app: variables y arranque
Defaults: template: "node" (Node 22 + npm; ubuntu no trae Node), appDir: "/app", port: 3000, build npm ci && npm run build, arranque npm start. Las variables no secretas (PORT, URLs, ids) van en env: se exportan antes del build y del arranque, y los secretos de la bóveda ganan por nombre.
npm start con node --env-file=.env muere en la caja porque no hay .env — arranca con node server.js y pasa la config por env + secretos. Express 5 rechaza app.all("*"): usa app.use(handler). Los repos públicos de GitHub clonan sin token.Hosting sin plan: la máquina se paga sola
No necesitas plan de pago para hostear. Una máquina se factura con su propia suscripción, así que desde una cuenta Free pagas tu caja y nada más. El plan sigue siendo el gate de IA, storage y flota — dejó de serlo para hosting.
create_machine devuelve una de dos cosas: con plan, la máquina lista y cobrada en la misma factura; sin plan, un checkoutUrl que le pasas al cliente. La máquina se crea sola en cuanto el pago se confirma — nada corre gratis mientras tanto. Cancelar la máquina no toca tu plan, y cancelar tu plan no se lleva la máquina.
Crear un sandbox permanente
Releases: que tu caja sea reconstruible
Fly y Vercel pueden tratar el disco como desechable porque cada deploy lo reconstruye desde una imagen. Aquí la app se escribe dentro de la caja, así que si la caja muere no pierdes solo los datos — pierdes la app. Un release lo resuelve: un tarball versionado de tu código en almacenamiento durable, más un runspec que dice cómo construirlo y arrancarlo. Con los dos, cualquier caja se reconstruye — y eso es también como se cambia de tier (no hay resize en caliente: se recrea).
Un release guarda código, no datos. Una caja recreada arranca vacía; los datos los cubre el backup.
Backups: incluidos, 7 días
Cada noche copiamos los dataPaths de tu runspec a almacenamiento durable fuera del host, con 7 días de retención y sin costo extra. No respaldamos el sistema operativo: eso se reconstruye del template, igual que Fly respalda volúmenes y no el rootfs. Al borrar una máquina tomamos una copia final y la guardamos 7 días: borrar destruye la VM en el acto, así que ese respaldo es la única vuelta atrás.
Dos cosas dichas de frente: el RPO es de 24 horas, y el backup se toma del filesystem en caliente, así que una base de datos escribiendo durante la copia puede quedar inconsistente — cada backup reporta su nivel en el campo consistency. Si tu app tiene una DB, tenla fuera de la caja (libSQL de EasyBits, Atlas) o toma un create_backup tras detenerla.
Promover un efímero a permanente
Levanta un sandbox, pruébalo, y si quieres conservarlo hazlo permanente — conserva el mismo sandboxId, desarma el reaper y arranca el cobro.
release_machine es destructiva (quita el cobro y destruye la VM). Si tu plan se cancela, tus sandboxes se suspenden.Variables secretas de tu app
Tu app necesita su DATABASE_URL, su STRIPE_SECRET_KEY. No las metas en runspec.env: eso se guarda en la base y viaja dentro de cada tarball de release. La API las rechaza por nombre.
Los valores se guardan cifrados en tu bóveda y en el runspec queda solo la lista de nombres. Se materializan dentro de la máquina —en un archivo que solo root puede leer— justo antes de construir y de arrancar. No entran al release: una caja reconstruida desde un tarball sigue sin llevarlos dentro, pero sabe cuáles pedir.
Surten efecto en el siguiente despliegue, no al vuelo: rotar un secreto es cambiarlo aquí y volver a desplegar. Si el runspec declara uno que no está en la bóveda, el deploy falla diciendo cuál — mejor que ver la app morir al conectar. También se administran en /dash/hosting → pestaña Variables.
Desplegar desde GitHub en cada push
El patrón recomendado para el sitio de un cliente: construir en el runner de GitHub y mandarle a la máquina el resultado ya hecho. La caja no compila nada, así que un sitio que necesitaría 4 GB para bundlear cabe en micro.
Por qué sandboxId y archiveUrl juntos: sandboxId es el destino, no una fuente. Puedes mandar un artefacto ya construido a una máquina que ya existe — sin eso, el único sitio donde podría ocurrir el build sería dentro de la caja del cliente.
node_modules con sharp o better-sqlite3 compilado en macOS revienta en Linux. Cada despliegue publica un release, así que historial y rollback siguen funcionando igual.Bases de datos
Crea bases de datos SQLite aisladas para tus agentes y apps — una por cliente, proyecto o recurso. Corren sobre sqld (libsql-server), con scale-to-zero: no pagas cómputo cuando nadie consulta. Cada DB es un namespace independiente; tu agente las crea, consulta y llena sin que escribas backend.
Crear y consultar
Herramientas MCP del grupo databases
db_listMCP—
Listar tus bases de datos
db_createMCPname, description?
Crear una base de datos aislada
db_getMCPdbId
Obtener una base de datos
db_deleteMCPdbId
Eliminar la base de datos y todos sus datos (irreversible)
db_queryMCPdbId, sql, args?
Ejecutar una consulta SQL
db_execMCPdbId, statements
Batch de hasta 20 sentencias
db_importMCPdbId, table, columns, rows, onConflict?
Importar hasta 10,000 filas de una vez
database.created y database.deleted. Combínalos con la sección Webhooks para notificar sistemas externos.Secretos
Guarda credenciales (API tokens, OAuth, llaves) cifradas AES-256-GCM en tu cuenta. Un secreto es write-only: una vez guardado, su valor nunca se puede volver a leer por API ni MCP — solo se inyecta como variable de entorno dentro de un sandbox vía agent_run({ secrets: [nombre, ...] }). Es también donde vive el OAuth que usa tu Flota.
[A-Z_][A-Z0-9_]* (mayúsculas, dígitos y guiones bajos).Herramientas MCP del grupo secrets
secret_setMCPname, value
Crear o sobrescribir un secreto (cifrado; el valor no se devuelve jamás)
secret_listMCP—
Listar nombres, fecha de creación y último uso (nunca valores)
secret_deleteMCPname
Eliminar un secreto por nombre
LLM (OpenAI-compatible)
Un gateway OpenAI-compatible con tu misma llave de EasyBits. No necesitas cuenta con el proveedor ni otra API key: apuntas cualquier cliente OpenAI a nuestra base URL y el consumo se descuenta de tu saldo de tokens.
/api/v2/llm. Pegarle a /v1/chat/completions o /chat/completions a secas devuelve 404 — es el error más común al configurar un cliente.Base URL
Endpoints
| Método & ruta | Qué hace |
|---|---|
| POST /api/v2/llm/v1/chat/completions | Completions. Soporta stream: true (SSE) |
| GET /api/v2/llm/v1/models | Modelos disponibles (proxy al proveedor, caché 5 min) |
| GET /api/v2/llm/balance | Saldo: usado, restante, plan, recargas, fecha de reset |
| POST /api/v2/llm/recharge | Compra tokens extra |
Notas
- Modelos: no hardcodeamos la lista — pégale a
/models. Si omitesmodel, el default esdeepseek-chat. - La llave necesita scope
WRITE: gastar tokens no es una operación de lectura. Una llave de solo lectura recibe403 permission_error. - Se cobra
prompt_tokens + completion_tokensde cada respuesta contra tu saldo. Sin saldo:402 insufficient_quota, conusedylimitenmeta. - Headers de respuesta:
x-llm-tokens-remainingyx-ratelimit-remaining-requests— úsalos para no tener que pedir el balance en cada llamada. - CORS abierto, pero eso no hace segura una llave en el browser:
eb_sk_live_...es secreta y gasta tu saldo. Llama desde tu servidor. - Contextos largos cuestan: cada turno reenvía el historial completo y se cobra íntegro. Una conversación de 300K tokens paga 300K por turno. Compacta o recorta.
Llamadas
Salas de videollamada con grabación en HD, self-hosted (template livekit-svc). Tu agente crea la sala, los participantes se unen desde el navegador (cámara + pantalla compartida, sin instalar nada), y el servidor graba el layout completo en 1080p. Al terminar, el MP4 se sube a tus Archivos. Sin servidores de terceros, sin límite de duración.
sandbox: call_create, call_record, call_stop, call_status, call_files, call_transcript, call_destroy. Las llaves del servidor de video se generan solas — no necesitas cuenta en ningún proveedor ni pasar secrets.Crear una llamada y grabar
create levanta la sala y devuelve roomUrl — compártelo con los participantes. La sala se auto-destruye a las 3 horas si no la cierras antes.
Estado, archivos y cierre
status reporta si está grabando y quién está conectado; files lista las grabaciones; destroy cierra la sala limpiamente (sube grabaciones pendientes y libera la VM).
Transcript de la llamada
Al detener la grabación, la caja transcribe el audio con Whisper embebido (español, on-device — sin proveedor externo) y sube el .txt a tus Archivos. transcript devuelve el texto inline (no un link) más un status: transcribing (Whisper procesando, reintenta en ~1 min), ready (texto en text), failed, unavailable o no_recording. Con sandboxId = estado en vivo del box; sin él = el transcript más reciente de Archivos.
call_destroy, la sala se apaga sola al TTL de 3 horas.Cuenta & Uso
/usageObtén las estadísticas de uso de la cuenta: storage, conteo de archivos, info del plan
/providersLista tus proveedores de storage configurados
/keysLista tus API keys (solo con auth de sesión)
Errores & Límites
| Status | Significado |
|---|---|
| 400 | Petición inválida (params incorrectos) |
| 401 | No autorizado (API key faltante/inválida) |
| 403 | Prohibido (scope insuficiente) |
| 404 | Recurso no encontrado |
| 429 | Rate limited (demasiadas peticiones) |
| 500 | Error del servidor |
Todas las respuestas de error tienen la misma forma: un JSON { "error": "message" }, opcionalmente con campos extra (ej. code, status). Por MCP se devuelve el mismo payload con isError: true.
Todo endpoint de lista devuelve el mismo envelope: { items, nextCursor, hasMore, total? }. Cuando hasMore es true, regresa nextCursor como cursor (u offset para documentos/sitios) para traer la siguiente página.
Límites: 100 peticiones cada 15 minutos en todos los planes.
Tool Groups
Por defecto el servidor MCP carga solo el grupo core para minimizar el uso de tokens. Habilita grupos adicionales para desbloquear más capacidades.
| Grupo | Herramientas | Descripción |
|---|---|---|
| core | 12 | Archivos, DB, documentos, cotizaciones, estadísticas (default) |
| sandbox | 22 | MicroVMs Firecracker: crear, ejecutar, exponer puertos, agentes persistentes y one-shot |
| fleet | 3 | Agentes de la flota: listar, leer config y aplicar acciones de /capabilities |
| files | ~37 | Todas las ops de archivos: bulk, sharing, permisos, webhooks, imágenes, AI keys |
| docs | ~33 | Documentos: generación AI, refine, screenshots, structured docs |
| sites | ~8 | Sitios web: CRUD, upload, deploy |
| brand | ~8 | Brand kits, plantillas, temas |
| payments | 2 | Links de pago con MercadoPago (BYO): create_payment_link, list_payment_links |
| 6 | Email transaccional + contactos + broadcasts (send_email, add_contact, create_broadcast…) | |
| all | ~104 | Todo (incluye slides y agentes) |
