Documentación de la API
Sandboxes, web, archivos, bases de datos, documentos y hosting para agentes de IA
Pídele a tu agente que descubra EasyBits
No copies URLs: tu agente de código (Claude Code, Cursor, Codex, Ghosty Code) instala los skills y aprende solo cómo usar sandboxes, web, archivos, bases de datos y hosting.
npx skills add https://easybits.cloud export EASYBITS_API_KEY="eb_sk_live_…"
Instala los skills de easybits.cloud. Luego crea un sandbox `node` que sobreviva (suspendOnIdle), corre `node -v` adentro y dime el resultado con el sandboxId.
Tu API key se crea en el Dashboard de Desarrollador. Los skills se publican en /.well-known/agent-skills, cada sección de estos docs existe como markdown en /docs/<sección>.md y la REST API tiene referencia OpenAPI.
URL base: https://www.easybits.cloud/api/v2
Codex con tu plan de ChatGPT, en tus cajas
Inicias sesión una vez con tu cuenta de ChatGPT Plus o Pro y cualquier caja de Easybits corre Codex con tu plan, sin OPENAI_API_KEY. Un comando: easybits sb codex connect. Ver cómo →
Inicio rápido a mano
- 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
293 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 293 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 — https or loopback redirects only; public (PKCE) or client_secret_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 |
| /oauth/revoke | RFC 7009 | Revokes the whole grant (refresh + access). Users can also disconnect apps in Developer → Claude.ai |
Handshake flow
Notes
- Access tokens are HS256 JWTs, valid for 1 hour, with a single-use rotating refresh token.
- Consent screen: every client registered through DCR sees which app is asking and which domain it returns to before a code is issued.
resource(RFC 8707) is validated: tokens are only issued for this MCP. Full guide: Claude 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, { slug? }) | Crea un sitio (slug opcional), obtén id + URL |
| getWebsite(websiteId) | Obtén los detalles del sitio |
| updateWebsite(websiteId, params) | Actualiza nombre, slug o 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
CLI
easybits es la terminal de EasyBits: sandboxes, hosting, dominios, bases de datos, agentes y archivos sin escribir un curl. Está hecho para personas y para agentes de código: cada comando acepta --json y los códigos de salida son estables.
Para tu agente de código
Instala las skills de EasyBits en tu agente (Claude Code, Codex, Cursor…). Traen easybits-cli, que le enseña a usar este CLI, y las demás (easybits-sandbox, easybits-agent…):
Y pégale este prompt, cambiando lo del final por lo que necesites:
El agente te pasará una liga para entrar la primera vez; lo demás lo hace él.
Instalar
Requiere Node 22 o superior.
Login
easybits login usa OAuth2 con PKCE: abre el navegador, imprime también la URL y espera a que vuelvas; la sesión (con refresh) queda en ~/.easybitsrc y se renueva sola. La sesión se renueva sola antes de vencer, y una vez más si la API contesta 401. Si corres un comando sin sesión desde una terminal, el login arranca solo; sin terminal o con --json (un agente, CI) sale con código 3. Al volver del navegador verás la página de EasyBits.
La API key sale del Dashboard de Desarrollador. Orden de precedencia: variable EASYBITS_API_KEY > bandera --token > sesión del navegador > key guardada con login -. En CI usa la variable. La key nunca va en argv: ahí la ven ps y el historial de tu shell (por eso login - la lee de stdin y la valida antes de guardarla).
EASYBITS_URL apunta el CLI a otro servidor (por defecto https://www.easybits.cloud); también se lee baseUrl de ~/.easybitsrc.
Sandboxes
Alias: easybits sb …. Todo lo que va después de -- en exec es el comando, tal cual. En create: --template (default ubuntu), --size s|m|l|xl (según tu plan), --ttl <s> (vida antes de autodestruirse; en exec, --timeout es el tope del comando) y --no-wait (regresa sin esperar a running). bg es para lo que dura más que una llamada (un servidor de desarrollo, un build largo): exec espera, bg start regresa un execId de inmediato; un nohup dentro de exec no sobrevive. git corre dentro de la caja con salida estructurada: el token de un repo privado va en --git-token, mejor como $secret:NOMBRE de tu bóveda (uno literal se acepta con aviso: argv se ve en ps); un commit sin cambios responde nothingToCommit, no un error. logs trae 200 líneas por defecto; --since acepta la sintaxis de journalctl.
Hosting: máquinas permanentes
easybits deploy <máquina> es el atajo de machines deploy <máquina>. Los secretos van en un archivo dotenv o por stdin (--dotenv -), nunca en argv: ahí los ven ps y el historial de tu shell. secrets ls sólo muestra nombres; los valores no se pueden leer.
Runspec = cómo se construye, arranca y respalda la app: appDir, buildCommand, startCommand o unit, port, prebuilt, dataPaths, excludes, env (no secreto). runspec set se mezcla (el env por llave) y aplica en el siguiente deploy o reinicio; --file rs.json acepta tal cual la salida de runspec get --json, y las banderas ganan sobre él. Sin --data (dataPaths) no se respalda nada. redeploy es recuperación y cambio de tamaño a la vez (no hay resize en caliente): construye una máquina NUEVA desde --release (default: el más nuevo) con --tier; --replace libera la vieja cuando la nueva corre (te pide teclear el id), si no, pagas las dos. La nueva nace sin datos. rm también pide teclear el id: deja de cobrarse, toma un último respaldo y destruye la caja.
Secretos de la cuenta (vault)
La bóveda de tu cuenta (la misma de /dash/developer/secrets y de las tools MCP secret_*). Un secreto se usa por nombre: $secret:NOMBRE en el MCP de un agente o en --repo-token, machines launch --secret NOMBRE. Los nombres son [A-Z_][A-Z0-9_]*; el mismo nombre reemplaza el valor. Los valores nunca van en argv (ps y el historial de tu shell los ven). machines secrets es otra cosa: valores para UNA máquina, que ganan sobre la bóveda.
Tus crons
Le pegan a una URL https pública (con --secret un secreto de tu bóveda viaja como header, Authorization: Bearer por default) o le mandan un turno a uno de tus agentes. Se repiten (--cron, como mucho cada 5 min; terminan solos con --until <fecha> o --times N) o se ejecutan una sola vez (--at "2026-10-18T23:00" o --at 23:00 = hoy, --in 2h): al ejecutarse se apagan y se quedan en ls como «terminado», con su historial. Una hora que ya pasó es error, no «mañana». Con --run el trabajo lo hace una caja desechable (python por default, --template node|bun|ubuntu): monta tus --file junto al comando, cada --secret NOMBRE de tu bóveda llega como variable de entorno y cada --secret-file NOMBRE:ruta sube ese archivo como secreto acotado a ese cron (ningún otro cron, agente ni máquina lo lee; se borra con el cron) y lo escribe en la misma ruta ~/ dentro de la caja; éxito = exit 0, tope --timeout 600 s, la caja se destruye al terminar. crons wait se queda hasta que termina la siguiente ejecución (o la que va en curso), imprime su salida y sale con su código (exec: el del comando; los demás: 0 si salió bien, 1 si no), así que encaja con un && o con un proceso en segundo plano. --notify push manda un aviso a la app de Ghosty con el token personal de tu secreto GHOSTY_PUSH_TOKEN; --notify email escribe al correo de tu cuenta (nunca a otro); por default sólo si falla, --notify-on always siempre. --dry-run valida todo y muestra las próximas 3 ejecuciones en hora local sin crear nada. Zona --tz (default America/Mexico_City), límite por plan. Misma API que /api/v2/crons.
Buzones de eventos
Un webhook de EasyBits que llega a tu terminal: lo que entra a la URL te alcanza en vivo con listen (en Claude Code, córrelo con Monitor: cada línea despierta al agente) o te espera para read. Un cron le escribe con --notify inbox:builds. Quien tenga la URL puede escribir: no la publiques.
Crons de la plataforma (sólo administradores)
Las tareas programadas de EasyBits (respaldos, purgas, caducidad de web apps): lo mismo que el panel /dash/admin/crons. Pide una llave con alcance ADMIN y ser superusuario de la plataforma; cualquier otra cuenta recibe 403. El horario vive en el código y no se edita desde aquí.
Dominios
Dos clases. Los de tu cuenta sirven tus sitios en <slug>.tienda.com y, si le asignas uno, en la raíz:
Los de una caja ligan un dominio a un puerto de un sandbox o máquina:
easybits domains <ls|add|verify|rm> <caja> … (la forma de antes) sigue funcionando: corre sandboxes domains y avisa en stderr. domains dns-auto sin Mailmask conectado sale con 1 y la liga para conectarlo una vez en el navegador.
Web
Buscar, leer, extraer y rastrear la web. Se mide en consultas web (paquetes en /dash/packs?tab=web); sin saldo sale con 1 y la liga para comprar.
extract cobra 1 consulta por registro devuelto (no por intento) y casi todas las fuentes tardan 30-120 s: sin --wait devuelve el jobId y lo revisas con extract-status. Fuentes: google_maps, mercadolibre, amazon_product, instagram_profiles, linkedin_company… (o --dataset <id> del catálogo).
Webhooks y API keys
El secreto del webhook (whsec_…) y la key nueva (eb_sk_live_…) sólo salen al crearlos: con --json vienen en secret y raw. Una key nunca puede crear otra con más permisos que los suyos (una READ no fabrica una WRITE, y sólo el panel o una key ADMIN da ADMIN). keys revoke avisa si es la key que usa el propio CLI.
Sitios (creador de sitios)
create devuelve {id, name, kind, stack, template, previewUrl, url, editorUrl}; url es null hasta que publiques desde el editor (construir es gratis; una web app se cobra al publicarla). --kind webapp (default) tiene su propia máquina con --stack tanstack (default), next, react-router, remix, nuxt, sveltekit, astro o elysia; --template (consultorio, despacho, portafolio, lanzamiento) sólo existe en TanStack y React Router, y sin ella el prompt elige una. --kind static es un website en el CDN. El CLI rechaza un stack o una plantilla desconocidos antes de llamar a la API.
theme es el botón «Tema» del editor: reescribe el archivo de tema del sitio (web app: eb-theme.css; estático: el bloque eb-theme de index.html) y la vista previa lo toma. --colors necesita todos los tokens (primary, primary-light, primary-dark, secondary, accent, surface, surface-alt, surface-deep, on-surface, on-surface-muted, on-surface-deep, on-primary, on-secondary, on-accent); una paleta desconocida responde invalid_theme y un sitio anterior a los temas responde no_tokens (409).
Documentos
Páginas de HTML que tú escribes (el editor de /dash/documents), publicadas en una URL pública y exportadas a PDF o PNG. El CLI no genera con IA: escribe el HTML de cada página y fíjalo.
Una página se indica por su número (1 = la primera, como en el editor) o por su id. --format es letter (default), ig-feed, li-feed, ig-square, fb-square, ig-story, wsp-status, tiktok, slide-16-9 o WxH en px (100-10000); sin --html el documento arranca con una página en blanco de ese tamaño. Las páginas pasan por el mismo camino que set_page_html: un HTML completo (doctype, <style>) se normaliza a página. export renderiza en el servidor (Chromium), así que los tamaños salen exactos sin importar tu máquina: pdf escribe un archivo (default <nombre>.pdf), png un archivo por página en una carpeta (default .) y además los guarda en tus Files. rm borra el documento para siempre.
Video
Un proyecto de video es una lista ordenada de escenas: cada una es HTML (posicionado en absoluto para llenar el cuadro; los medios como assets/<nombre>), un fragmento GSAP opcional sobre un timeline pausado tl y una narración opcional que se sintetiza al renderizar (kokoro: em_santa por default). El servidor lo compila a una composición HyperFrames y renderiza el MP4 en tu caja on-demand.
Una escena se indica por su número (1 = la primera) o su id; el proyecto, por nombre o id. --timeline toma un archivo o el código GSAP tal cual; --narration "" quita la voz en off y --duration (0.3-120 s) por default es lo que dure la narración. render sin --wait arranca el render y regresa en cuanto el servidor lo marca rendering; con --wait muestra un spinner (renglones simples sin terminal, nada con --json) hasta que el proyecto queda ready (imprime la URL del MP4; --out lo baja) o failed (sale con 1 y el motivo). Si un proxy corta la petición larga, el CLI sigue el estado del proyecto (--timeout, 15 min por default). La música es una URL pública: sube antes un archivo local con easybits files upload cancion.mp3 --public.
Bases de datos
query y tables aceptan el id o el nombre, y nunca crean una base por un nombre mal escrito. Con --json, tables da [{name, rows, columns:[{name,type,pk}], links:[{column, permanent, expiring, external, empty}]}]: por cada columna de texto con ligas, cuántas son permanentes (almacenamiento público de EasyBits), caducan (URLs firmadas), son de fuera o están vacías.
Adjuntar: db attach <nombre> agrega a tu cuenta la base que una de tus máquinas ya usa (DATABASE_URL=http://172.20.0.1:8090/v1/db/<nombre>/). No crea ni copia nada; desde ahí sirven db query|tables|ls. No cuenta contra el límite de bases de tu plan y db rm sólo la quita de tu lista: los datos se quedan en la máquina.
Renombrar: db rename <db> <nombre-nuevo> [--skip-prompts] [--dry-run] cambia el nombre (el id no cambia, así que las tools db_* siguen funcionando) y reescribe el nombre en los prompts de tus agentes ghosty-lite/goose donde aparece como palabra suelta: nunca dentro de x.com, @x, x_prueba ni en mayúsculas (la marca). --dry-run enseña cada prompt y renglón; si un prompt falla se reporta (sale con 1) y el nombre nuevo se queda. Skills y env no se revisan.
Fotos del catálogo: db photos put <db> --table T --key-column sku --dir fotos/ [--column C] [--replace] [--dry-run] pone SKU-123.jpg en la fila cuyo sku es SKU-123: revisa el tipo real por sus bytes (JPEG, PNG, WebP; máx 10 MB; avisa arriba de 5 MB, el tope de WhatsApp por liga), la sube como archivo público con liga permanente (la misma foto para dos filas se sube una vez) y escribe la liga en la columna (default image_url, photo_url, imagen…). Una fila que ya tiene liga permanente se queda salvo --replace; lo que no es foto o no tiene fila se enlista con el motivo.
Agentes
Configurar un agente con máquina (ghosty-lite, goose; mismo contrato que la CLI ghosty de Ghosty Studio). Toda escritura acepta --dry-run:
Clonar (--like / --from) copia template, prompt, servidores MCP y skills (con --copy-files, también los archivos), nunca el env: las llaves del motor van con --dotenv. export enmascara los secretos del MCP salvo con --show-secrets; las referencias $secret:NOMBRE viajan tal cual.
Agentes de flota
Un agente de flota rutea cada conversación (WhatsApp, WABA, Teams, web) a una caja efímera; se crea y se ligan sus canales en /dash/flota. El CLI usa la credencial de tu cuenta, nunca el token del agente. configure aplica un cambio a la vez (el motor antes que el modelo; --recycle al final): el cambio llega a las cajas dormidas al despertar y a una corriendo cuando se duerme, o de inmediato con --recycle (--engine recicla solo). --name cambia el nombre visible y el nombre con el que se presenta. message manda un turno e imprime la respuesta; sin --group abre una conversación nueva cli-<uuid> y te dice su id en stderr. Pasa --config-group (web, waba:<id>, un grupo de WhatsApp) para cargar las tools, el MCP y el prompt de ese canal: sin él, el turno puede correr sin los conectores del canal.
El agente como archivo
agent.yaml lleva template, name, prompt + promptMode, env (no secreto), mcp, skills y files (--out archivo.yaml o .json escribe sólo el archivo; sin --out sale a stdout). Los secretos nunca salen: las llaves con cara de secreto (KEY, TOKEN, AUTH…) y las URLs con contraseña se exportan como ${NOMBRE}; al aplicar, el CLI manda sólo los valores de los ${NOMBRE} que el archivo menciona y existen en tu entorno, y uno sin valor conserva el de hoy. $secret:NOMBRE (el vault) viaja tal cual. El env de la plataforma (la llave de EasyBits del agente, tokens internos) ni se exporta ni se toca.
El plan: + agrega, ~ cambia, - quita, ! no se aplica y dice por qué. Declarativo: lo que el archivo no menciona no se toca; quitar lo que el archivo ya no lista exige --prune. El plan lo calcula el servidor (es el único que ve el env y los valores del MCP de hoy). Env, MCP y skills reinician el agente una vez al final. Antes de aplicar se guarda el archivo anterior en ~/.easybits/backups/ (para volver: easybits apply <respaldo> --agent <agente>). Límites: un agente no cambia de template (usa --create) y los agentes sin máquina propia sólo toman name. --show-secrets / --with-files siguen dando el JSON de clonado de 0.9 para create --from.
Autocompletado
TAB completa comandos, subcomandos y flags (de la misma tabla que la ayuda) y los nombres de tus agentes, sandboxes/máquinas y bases donde va uno (easybits agents get <TAB>, easybits db query <TAB>, --agent <TAB>). Los nombres se guardan 5 minutos en ~/.cache/easybits/.
Idioma
El CLI habla español o inglés según tu locale: LANG=es_MX.UTF-8 (o LC_ALL/LC_MESSAGES) → español; cualquier otro → inglés. Fuérzalo con --lang es|en o EASYBITS_LANG. Cambia la ayuda, los errores, las pistas y la salida para personas; las llaves de --json y los códigos de salida nunca cambian (error y hint son prosa, así que siguen el idioma).
Cómo se nombran los comandos
Una sola regla, como gh: easybits <sustantivo> <verbo>. Las formas viejas siguen funcionando y avisan la nueva en stderr (nunca con --json): easybits config → mcp config, easybits mcp → mcp config --stdio, deploy ls → machines ls (easybits deploy <máquina> es el verbo), machines release → machines deploy, sandboxes create --timeout → --ttl.
Nombres en vez de ids
Todo comando que recibe un agente, un sandbox o máquina, una base de datos, un sitio, un documento o un proyecto de video acepta su nombre además del id: easybits sb exec pruebas -- ls, easybits sb domains ls tienda, easybits machines deploy tienda -m "v2". Un id (sb_…, o el id de 24 caracteres de agentes, bases, sitios, documentos y proyectos de video) pasa directo; un nombre se busca exacto, sin distinguir mayúsculas, en tu lista (una petición por ejecución). Si dos comparten el nombre, el CLI sale con 2 y enlista los ids; si ninguno coincide, sale con 1 y te dice qué ls correr.
Archivos, sitios y cuenta
MCP, SSH y docs
SSH por el 443 — agrega a ~/.ssh/config y entra con ssh <nombre>.ghosty:
--json y códigos de salida
Con --json stdout lleva sólo JSON (sin banner, sin tablas), errores incluidos: {"error":"…","code":3,"hint":"…"}, donde code es el código de salida. Es el único canal que tiene que leer un agente; los avisos para humanos van a stderr.
| Código | Significa |
|---|---|
0 | ok |
1 | error de la API (4xx/5xx), o domains verify todavía no listo |
2 | error de uso: comando, subcomando o argumento faltante o desconocido |
3 | sin sesión (sin terminal), sesión vencida, o credencial rechazada (401) |
Excepción deliberada: sandboxes exec sin --json sale con el código del comando remoto (como ssh); con --json sale 0 y el código viaja en exitCode.
Borrados: --yes
Todo rm y destroy (sandboxes destroy, machines rm, agents destroy, db rm, domains rm, files rm…) y machines redeploy --replace preguntan antes de actuar en una terminal: [y/N], o teclear el nombre/id en lo irreversible (una base, un agente, una máquina o caja permanente). Sin terminal o con --json nunca preguntan: pasa --yes (-y) o salen con 2 y la pista con el comando exacto.
Como mucho una vez por hora revisa npm y, en una terminal, el CLI avisa en stderr cuando hay versión nueva (nunca con --json; EASYBITS_NO_UPDATE_CHECK=1 o CI lo apagan).
easybits --help, easybits <comando> <subcomando> --help y easybits help <comando> <subcomando> muestran uso, banderas y ejemplos. Un typo recibe un «Did you mean».
Para agentes de código
- Usa siempre
--jsony decide por el código de salida, no por el texto. - Si no hay sesión, corre
easybits login --json: la primera línea es{"event":"login_url","url":…}— muéstrale esa liga a la persona — y cuando entra llega{"event":"logged_in","email":…}. Con--no-browserno abre el navegador de la máquina. - En CI, o si la persona te da una key, usa
EASYBITS_API_KEY. - Encadena con
jq:ID=$(easybits sb create --template node --json | jq -r .sandboxId). - En
exec, separa el comando con--y usa--jsonpara leerstdout,stderryexitCodejuntos. - Borrar con
--jsonexige--yes; sin él sale con2. - Antes de tocar la config de un agente, previsualiza con
--dry-run; después, verifica coneasybits agents try <agente> "…"en vez de suponer que funcionó. - Destruye lo que crees:
easybits sb destroy $ID --json --yes. - Skill lista para instalar:
npx skills add https://easybits.cloud(incluyeeasybits-cli).
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. Cada respuesta trae hasta 200,000 caracteres. Si truncated=true, pide el resto con { fetchId, offset: nextOffset }: gratis, de la misma copia (dura 24 h).
/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 al momento (default 10); 21-2000 en segundo plano: responde 202 { jobId } y se consulta en GET /web/crawl/:jobId. 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.
/web/mapTodas las URLs de un sitio desde su sitemap (o los links de la portada), sin leerlas. Úsalo antes de leer un sitio grande
| url | string | URL del sitio (requerido) |
| search | string | Sólo URLs que contengan este texto, p. ej. /blog/ |
| limit | number | 1-5000, default 1000. Cuesta 1 consulta por documento leído (normalmente 1-3) |
Mapea, elige y lee sólo esas páginas con /web/fetch: más barato que rastrear a ciegas.
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.
web_crawl_statusMCPjobId, cursor?
Estado y páginas (por bloques) de un rastreo grande. Gratis.
web_interactMCPurl, actions?, screenshot?
Navegador remoto: clics, escribir, Enter, scroll; devuelve la página final. 5 consultas por sesión.
web_monitor_createMCPurl, everyMinutes?
Vigila una página y avisa por webhook (web.page.changed) cuando cambia. 1 consulta por revisión.
web_monitor_listMCPTus monitores con revisiones y cambios detectados.
web_monitor_deleteMCPmonitorId
Deja de vigilar una página.
web_mapMCPurl, search?, limit?
Todas las URLs de un sitio desde su sitemap, sin leerlas. 1 consulta por documento leído.
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.updated, 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) |
| slug | string | Opcional. URL pública /s/<slug>/: a-z, 0-9 y guiones, 3–60, único. 400 slug_invalid / 409 slug_taken |
/websites/:websiteIdObtén los detalles del sitio
/websites/:websiteIdActualiza el nombre, el slug o el status del sitio. Al cambiar el slug, el viejo responde 301 al nuevo
| name | string | Nuevo nombre |
| slug | string | Nuevo slug (mismas reglas que al crear). 400 slug_invalid / 409 slug_taken |
| 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 |
|---|---|---|
| ubuntu | base | Linux completo. Instalar paquetes, compilar, correr servidores. |
| python | base | Runtime Python; cada run-code es un proceso fresco. |
| node | base | Node 24 + typescript, tsx, pnpm, git y python3; cada run-code es un proceso fresco. |
| bun | base | Runtime Bun. |
| dev-box | base | Caja de trabajo limpia (git, curl, build-essential, Node 22); la recomendada para SSH. |
| code-interpreter | base | Python + kernel Jupyter persistente (sandbox_run_cell): variables y gráficas sobreviven entre celdas. |
| eve-nitro | base | Servidor eve (Vercel) self-hosted: Node 24, pnpm, eve CLI 0.65; /data persistente, puerto 3000. |
| node-agent | agente | Node + Claude Agent SDK pre-horneado (agent_run). |
| claude-code | agente | Loop del Claude Agent SDK; billing por token. |
| goose | agente | goose (AAIF), agente de código con ACP nativo. |
| ghostyclaw | agente | Daemon always-on de Ghosty (WhatsApp, Slack, Telegram) con Docker y admin-api. |
| ghosty-lite | agente | Agente ACP ligero en Rust, multi-provider; el cerebro puede ser tu llave EasyBits. |
| open-ghosty | agente | Ghosty sobre modelos abiertos, chat web SSE. |
| lang-ghosty | agente | Ghosty sobre LangChain, chat web SSE. |
| rust-ghosty | agente | Ghosty DeepSeek-first (CodeWhale/Rust) con web SSE y WhatsApp. |
| ghosty-gc | agente | Ghosty para equipos (GTeams): hilos, artefactos, editor colaborativo. |
| ghosty-chat | agente | Chat Ghosty persistente (Express + SSE). |
| cagent-ghosty | agente | Ghosty sobre cagent (Docker), chat web SSE. |
| openclaw | agente | OpenClaw, IA personal always-on. |
| chat-openai | agente | Chat persistente Express+SSE con OpenAI; créalo con agent_create. |
| chat-anthropic | agente | Chat persistente Express+SSE con Anthropic; créalo con agent_create. |
| ghosty-studio | agente | Ghosty Studio: control plane de agentes dentro de una caja. |
| desktop-ghosty | agente | Escritorio Linux con Ghosty (noVNC). |
| computer-ghosty | agente | Computer-use con escritorio XFCE + noVNC público. |
| computer-ghosty-gemini | agente | Computer-use con Gemini. |
| livekit-svc | servicio | Sala de videollamada + grabación HD (Studio). |
| whisper-svc | servicio | STT whisper; parte de la caja voice. |
| kokoro-svc | servicio | TTS kokoro; parte de la caja voice. |
| voice-svc | servicio | Voz (STT + TTS) para la flota; service_start('voice'). |
| render-svc | servicio | Chromium para PDF/PNG/auditoría; service_start('render'). |
| collab-svc | servicio | Editor colaborativo (Yjs) de GTeams. |
| hyperframes-svc | servicio | Render de video HyperFrames. |
| claude-worker | interno | Worker de la flota (Claude). Lo crea la plataforma. |
| codex-worker | interno | Worker de la flota (Codex). Lo crea la plataforma. |
| gemini-worker | interno | Worker de la flota (Gemini). Lo crea la plataforma. |
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/template-snapshot | key*, hash*, name | Captura la caja preparada como plantilla derivada (idempotente por key+hash) |
| GET /template-snapshots | ?key=&hash= | Lista plantillas derivadas (o resuelve un par) |
| DELETE /template-snapshots/:id | — | Borra una plantilla derivada (409 si tiene hijas vivas) |
| POST /sandboxes/:id/network-policy | policy* (allow-all | deny-all | { allow }) | Política de egress por caja |
| 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.
Plantillas derivadas (template-snapshot)
Preparas una caja (npm install, seeds, skills) y la capturas como plantilla derivada bajo una clave de contenido (key, hash). Desde entonces cada caja creada con ese par nace con el bootstrap hecho: sin fork ni copia por hijo, el host guarda sólo el delta de disco (12-22 MB). Medido: captura ~0.5-1 s; create desde la derivada ~0.6 s + boot ~2 s. Es lo que usa @easybits.cloud/eve-sandbox para el prewarm de eve. Diferencia con snapshot + fork: la derivada es disco solo (sin memoria) y el hijo arranca en frío; el fork ramifica una caja viva con su memoria.
/etc/sandbox-env/env y /app/secrets.env, y cada hija recibe su propio env en el create. Una plantilla sin uso 30 días se borra sola; si el template base se rehornea, el create responde 409 DerivedTemplateStale y hay que volver a capturar.Política de red por caja (network-policy)
Sólo los dominios que autorices para esa caja, o ninguno: "allow-all", "deny-all" o una allow-list por dominio ({ allow: { "api.github.com": [], "registry.npmjs.org": [] } }, sin comodines). El host la resuelve a IPs por microVM con refresco DNS, la persiste con la caja y la vuelve a aplicar al despertar. SDK: sb.setNetworkPolicy(policy) · REST: POST /sandboxes/:id/network-policy · MCP: sandbox_set_network_policy.
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_set_idleMCPsandboxId, suspendOnIdle, idleTtlSeconds
Siesta en una caja YA creada: al vencer el TTL se suspende en vez de destruirse
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
Skill para tu agente:
npx skills add https://easybits.cloudinstalaeasybits-sandbox(la API de cajas) yeasybits-cli(lo mismo desde la terminal:easybits sandboxes …).
Actividad y errores de una caja
GET /sandboxes/:id (SDK sb.refresh(), MCP sandbox_status) trae activity mientras corre una operación larga sobre la caja: "snapshotting" o "forking". Sin operación en curso, el campo no viene. Mientras está presente, exec, suspend, destroy, snapshot y fork contestan 409 SandboxBusy al instante: espera a que desaparezca y reintenta.
Los errores de una caja traen { "error": "<código>", "message": "…" }:
| Status | error | Qué pasó | Qué hacer |
|---|---|---|---|
| 403 | AgentLimitReached | Tu plan ya tiene todas sus cajas persistentes (agentes ACP o de marca), vivas o dormidas: Byte 1, Mega 2, Tera 5 | Borra un agente (existing trae cuáles) o sube de plan (upgradeUrl) |
| 409 | SandboxBusy | Hay un snapshot o fork en curso sobre la caja | Consulta activity con GET /sandboxes/:id y reintenta cuando desaparezca (suelen ser minutos) |
| 409 | SandboxNotReady | La caja aún está en starting (o dormida) | Espera status=running, o resume si está suspended |
| 409 | SandboxUnreachable | El host no alcanza al agente dentro de la caja (reiniciando o colgada) | Espera y reintenta; si sigue, destrúyela y crea otra |
| 504 | SandboxHostTimeout | El host no respondió a tiempo; la operación puede seguir en curso | Revisa GET /sandboxes/:id y reintenta en unos minutos; no la repitas a ciegas |
| 502 | SandboxHostError | El host de cajas falló (status y message traen su error) | Reintenta; si persiste, contacta a soporte con el message |
Un snapshot puede tardar varios minutos (copia disco y memoria); la API espera hasta 10.
Codex con tu plan de ChatGPT
Si pagas ChatGPT Plus o Pro, ya tienes Codex incluido. Aquí lo corres en tus cajas de Easybits con ese mismo plan: sin OPENAI_API_KEY y sin pagar tokens aparte. Inicias sesión una vez con tu cuenta; tu sesión queda cifrada en tu vault (secreto CODEX_AUTH) y se pone en cualquier caja con un comando.
codex siguen pidiendo OPENAI_API_KEY.1. Inicia sesión una vez
En cualquier caja con Node. El comando instala Codex, te da un link y un código, espera a que entres y guarda la sesión en tu vault:
Si OpenAI te dice que el código no está permitido, activa inicio de sesión con código de dispositivo en la configuración de seguridad de ChatGPT (en un workspace lo activa el admin). ¿Ya tienes ~/.codex/auth.json en tu compu? Súbelo y te saltas este paso: easybits secrets set CODEX_AUTH < ~/.codex/auth.json.
2. Conecta cualquier caja
Para que la sesión no se muera
- OpenAI cambia el token cada vez que Codex lo renueva. Por eso
connectnunca reemplaza una sesión que la caja ya tenga (con--forcesí). - Al borrar una caja por la API, su sesión renovada se guarda sola en tu vault. A mano:
easybits sb codex save sb_…. - Una sesión, una caja a la vez: dos cajas renovando el mismo token se lo pisan y una tendría que volver a iniciar sesión.
- En la caja vive en
~/.codex/auth.jsoncon permisos 600 y entra por stdin, así que no aparece en la lista de procesos.
Una caja nueva vive 30 minutos por default: créala con --ttl para trabajar más, o usa una máquina permanente. Más contexto en el post del blog.
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.Su system prompt (identidad)
Por default el agente llega con la persona de Ghosty (habla en el idioma en que le escriben). Si quieres el tuyo, mándalo al crear con env.SYSTEM_PROMPT; con SYSTEM_PROMPT_MODE eliges si reemplaza a la de casa (replace) o se le anexa (append, el default). Después de creado se cambia con PATCH y se lee con GET …/prompt; entra sin reboot.
Una conversación ya abierta conserva el prompt con el que arrancó: el nuevo aplica a la siguiente sesión.POST /api/v2/agents/ghosty y /agents/autonomous aceptan los mismos systemPrompt / systemPromptMode en el body.
Configurarlo por API: archivos, skills, MCP, reinicio
Después de creado, el agente se configura sobre su máquina con el mismo contrato que Ghosty Studio, así que la skill ghosty-agent (npx skills add https://www.ghosty.studio) sirve aquí cambiando la base a https://www.easybits.cloud/api/v2/agents/$AGENT_ID y el bearer a tu EASYBITS_API_KEY.
| Ruta | Métodos | Qué hace |
|---|---|---|
| /files/<ruta> | GET · PUT · DELETE | Archivos de conocimiento en /data/work. PUT con los bytes crudos (máx 10 MB). Sin reinicio. |
| /skills/<slug> | PUT · DELETE | JSON { markdown, assets?: [{ name, contentBase64 }] }. Entra tras restart. GET /skills las lista. |
| /mcp | GET · PUT | PUT { servers: [...] } reemplaza la lista entera y reinicia solo. |
| /restart | POST | Rearranca el agente y abre sesión nueva (se pierde la conversación en curso). |
| /prompt | GET · PATCH /agents/:id | Identidad (ver arriba). |
Sólo para templates con máquina propia (ghosty-lite, goose); en los demás estas rutas responden 409 agente_sin_maquina. Una skill con un : sin comillas en su frontmatter se rechaza con 400 (el cargador la descartaría en silencio).
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.eve (Vercel) sobre EasyBits
eve es el framework open-source de Vercel para agentes: un agente es un directorio y cada sesión es un run durable. Cuando un agente necesita ejecutar código, eve le pide una caja a un provider de sandbox. @easybits.cloud/eve-sandbox es ese provider: cada sesión de agente corre en su propia microVM, en tu cuenta. Requiere Node ≥ 24 y eve ≥ 0.64 (para eve ≤ 0.63 fija @easybits.cloud/eve-sandbox@0.1). Ruta gratis: tu servidor eve se queda donde está (Vercel o tu laptop con eve dev/eve start) y sólo las sesiones corren en EasyBits; cabe en Byte (una conversación a la vez, sesiones de 1 h). Ruta hospedada: el servidor eve también en una caja (eve-nitro) + una caja por sesión = 2 cajas, desde Mega ($499 MXN/mes); eve-world sólo aplica ahí.
eve build llama a prepare(): caja temporal node + agent/sandbox/workspace/ en /workspace + skills en ~/.agents/skills + tu prepare(sandbox) → plantilla derivada (template-snapshot, clave de contenido, 12-24 MB, se borra sola a los 30 días sin uso; medido: 9.7 s la primera vez, 0.3 s reusada). environment.open() → start(): la caja nace de esa plantilla con todo hecho (~5 s) y entre turnos duerme y despierta en ~1 s; si el servidor reinicia, resume() la reabre, y si ya no existe falla (contrato de eve) y eve abre una sesión nueva. Egress por caja: environment.open({ networkPolicy }) o sandbox.setNetworkPolicy() en caliente (allow-all, deny-all o allow-list por dominio; transform no está soportado). Modelo: el scaffold trae un string del AI Gateway de Vercel; fuera de Vercel usa anthropic() de eve/models/anthropic (lee ANTHROPIC_API_KEY) u openai(). En local eve start responde 401 aun desde localhost: usa eve dev --no-ui. Para hospedar el servidor eve usa el template eve-nitro (Node 24, pnpm, eve CLI 0.65, /data persistente —no es el cwd: cd /data explícito—, puerto 3000), espera status=running antes de exec (sobre una caja starting responde 409 SandboxNotReady), instala con pnpm add … --allow-build=cbor-extract (pnpm 12 bloquea ese build script), sube los archivos del agente con POST /sandboxes/:id/files/write, corre npx eve build con EASYBITS_API_KEY en el env y expón el 3000; en producción eve exige auth en su API HTTP (edita agent/channels/eve.ts, p. ej. httpBasic). El estado durable (runs, hooks, streams) puede vivir en EasyBits DB con @easybits.cloud/eve-world (la caja nace con EASYBITS_DB_URL en su entorno, sin token; para retomar en una caja nueva, créala con el mismo valor, que está en metadata.eve_db_url). Landing: /eve · guía completa: /docs/eve.md · skill easybits-eve.
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 24 + npm/pnpm; 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.Tareas programadas de la plataforma (sólo administradores)
Los respaldos diarios de máquinas, su rotación, el monitor de hosting (cada 5 min) y las purgas semanales son tareas programadas de EasyBits. Su horario vive en el código y cada ejecución queda registrada (hora, duración, quién la disparó, resumen y error). Se ven y operan en /dash/admin/crons o por API. Requieren scope ADMIN y ser administrador de la plataforma; cualquier otra cuenta recibe 403.
GET /api/v2/admin/crons—{ items }: por tareakey,name,path,cron,schedule(en palabras),status(al-dia·tarde·fallo·pausado·nuevo),paused,running,nextRunAt,lastRunyrecent(últimas 20, la más reciente al final). «tarde» = pasó la hora programada y sigue dentro de la gracia (10 % del periodo, mínimo 5 min); «fallo» = pasó la gracia o la última ejecución terminó con error.GET /api/v2/admin/crons/:key/runs— últimas 50 ejecuciones con el error completo.POST /api/v2/admin/crons/:key/run— la ejecuta en segundo plano (trigger: "manual") y responde 202 al instante. 409 si ya está en ejecución o en pausa.POST /api/v2/admin/crons/:key/pause—{ "paused": true | false }. En pausa, la ruta responde{ "skipped": "paused" }sin ejecutarse.
Asistente del panel
En /dash/admin/crons hay un asistente (un agente de la flota, «Asistente de admin», uno por superusuario) que responde sólo con datos de estas tools MCP del grupo admin-crons:
list_crons— el mismo estado queGET /api/v2/admin/crons.get_cron_runs({ job, limit? })— últimas ejecuciones con el error completo; con esto explica por qué falló una tarea.run_cron({ job })ypause_cron({ job, paused })— no ejecutan: crean una acción pendiente y devuelven{ needs_confirmation: true, actionId, summary }(éxito, no error, para que el modelo no reintente). El resumen se arma con el nombre real de la tarea, no con texto del modelo.
Las cuatro exigen ser superusuario; las dos que proponen piden scope WRITE, no ADMIN: la llave del asistente vive en su microVM y con ADMIN podría saltarse la confirmación llamando la API directa.
Confirmación: el chat habla AG-UI (POST /api/v2/admin/copilot/chat con un RunAgentInput → SSE de eventos AG-UI; contrato en docs/protocol.md de ghostykit). Cada propuesta termina el run con RUN_FINISHED outcome: { type: "interrupt", interrupts: [{ id, message, expiresAt, metadata: { tool } }] }. Se contesta con otro POST al mismo hilo: resume: [{ interruptId, status: "resolved", payload: { approved: true | false } }], usando sólo la sesión del superusuario que la propuso (cualquier Authorization → 403). Se ejecuta una sola vez (repetir el resume no vuelve a ejecutar) y vence a los 15 min (RUN_ERROR interrupt_expired). Un POST sin mensaje ni resume reconecta: MESSAGES_SNAPSHOT y el turno vivo o las tarjetas abiertas.
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.Skill para tu agente:
npx skills add https://easybits.cloudinstalaeasybits-cli:easybits db create|query|tables|ls|rmcon--json.
Errores
Los errores de consulta traen { "error": "…", "code": "<código>" }:
| Status | code | Qué pasó | Qué hacer |
|---|---|---|---|
| 400 | SQL_ERROR | La sentencia falló (sintaxis, tabla inexistente, constraint); error trae el mensaje de SQLite | Corrige el SQL |
| 409 | DATABASE_STORAGE_MISSING | La base existe en tu cuenta pero su almacenamiento no está en el servidor; sus datos no están disponibles | Bórrala y crea otra, o contacta a soporte |
| 502 | DATABASE_BACKEND_ERROR | El servidor de bases falló | Reintenta en un momento |
| 404 | — | La base no existe o no es tuya | Revisa el id con GET /databases |
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 |
|---|---|---|
| design | 95 | Canva-like: documentos como diseño universal (cualquier formato — letter, social, slide 16:9), brand kits e imágenes. Ideal para Claude.ai / Claude Design. |
| web | 15 | Internet para tu agente: buscar en Google, leer cualquier página aunque bloquee bots, extraer registros con esquema (Maps, Mercado Libre, Amazon, Instagram…) y rastrear sitios. Se mide en consultas (packs en /dash/packs). |
| core | 114 | Lo esencial: archivos, DBs, documentos, forms, websites, brand. Para agentes generalistas. |
| ghosty | 29 | Curado y mínimo para agentes Ghosty (DeepSeek): set DB completo + documentos + archivos + imagen (gpt-image-2). Sin brand/forms/websites/media para no floodear la elección de tool. |
| ghostyapp | 10 | Para el agente del TELÉFONO (app Ghosty): imágenes, buscar y leer la web, fotos e iconos de stock, captura de una página y link para compartir. Deliberadamente sin DB, sites, forms ni brand: nadie hace eso desde un móvil, y cada tool de más empeora la elección. |
| docs | — | Solo tools de documentos (create/update/deploy + quotations + structured_doc). |
| sites | — | Solo tools de websites (crear, deployar archivos, inject HTML, dominios propios). |
| brand | — | Solo tools de brand kits (CRUD + extract desde URL). |
| magnet | 20 | Toolset orquestado para crear lead magnets (PDF + landing + form). |
| video | 23 | Video con IA (Runway Gen-4.5) + personajes recurrentes, y proyectos de video doc-style (escenas animadas HTML → MP4 con narración kokoro). Ghosty arma escenas, música y voz, y renderiza. |
| sandbox | 81 | MicroVMs Firecracker para correr agentes y código aislado. Ciclo de vida (spawn/extend/suspend/resume/destroy), snapshot + fork (clonar una caja viva en N hijos copy-on-write para explorar variantes en paralelo), exec (sync + background), run-code, kernel Jupyter persistente (run_cell con estado + charts), archivos (write/read/list/delete/move/mkdir), expose_port (URL pública), sandbox_admin (passthrough admin :8787) + agent_run (Claude managed con billing por token). Ideal para harness de agentes (Claude Code, Codex) y ejecución segura. |
| fleet | 3 | Agentes de la flota (WhatsApp, WABA, web, Teams): listar, leer su configuración y aplicar cualquier acción de /capabilities por MCP — prompt, motor, modelo, secrets, conectores (add-mcp + set-cap-level), skills, buckets por canal, reciclar caja. Lo que hace el dashboard de /dash/flota, hecho por un agente. |
| hosting | 35 | Hosting de apps sobre microVMs Firecracker, always-on, tiers fijos en MXN/mes (nano…performance). launch_app pone una app en producción en UNA llamada (caja + código + build + URL pública + release + dominio con TLS) — el equivalente a `fly launch`. Además: releases con rollback y redeploy (que es también el cambio de tier), backups diarios de datos incluidos con 7 días de retención, dominios propios, logs, y delegación de acceso para co-administrar. El plan da acceso; cada máquina factura flat al mes. |
| payments | 2 | Links de pago con MercadoPago (BYO): conecta tu cuenta y genera cobros. El dinero va directo a tu MP; EasyBits no retiene fondos. Ideal para que un agente cierre la venta. |
| 7 | Email transaccional + audiencia + newsletters one-shot. send_email, contactos con tags, y broadcasts con unsubscribe automático. La capa de nurturing del funnel. | |
| public-safe | 3 | Subset mínimo para agentes públicos (B2C / WhatsApp customer-facing): upload_file, create_share_link, db_select (read-only SQL con anti-stacking, anti-CROSS-JOIN, anti-sqlite_master). Sin listar workspace, sin eliminar, sin webhooks/websites/secrets, sin internet abierto. Diseñado para NanoClaw FORMMY_PUBLIC_TEMPLATE. |
| claude | 85 | La URL que se publica en el directorio de conectores de Claude (`/api/mcp/claude`): archivos, documentos, sitios, formularios, brand kits, bases de datos e investigación web. Sin generar imagen, video ni voz con IA, sin cobros ni hosting, sin secretos, webhooks, cajas ni flota. Strict: `run_tool` tampoco sale de aquí. |
| scripting | 3 | Superficie mínima para agentes que escriben SCRIPTS en vez de invocar tools. En vez de cargar las ~140 schemas (impuesto fijo de contexto que DeepSeek no admite), el agente usa discover_tools (buscar) + run_tool (ejecutar) y/o la REST API v2 desde su Bash. Solo deja en tools/list el IO de archivos (multipart incómodo por curl); todo lo demás se alcanza sin reconectar. Patrón Anthropic 'Code execution with MCP' / Cloudflare 'Code Mode'. |
| all | 293 | Todas las tools disponibles (~50+). Ideal para agentes avanzados. |
