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.

1. En tu terminal
npx skills add https://easybits.cloud
export EASYBITS_API_KEY="eb_sk_live_…"
2. Luego pídele, por ejemplo
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.

/docs/quickstart.mdAbrir en Claude ↗Abrir en ChatGPT ↗

URL base: https://www.easybits.cloud/api/v2

Nuevo

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 →

Otras formas de integrarte: REST API (abajo), SDK tipado (npm i @easybits.cloud/sdk), o servidor MCP (293 herramientas para agentes; por defecto solo el grupo core).

Inicio rápido a mano

  1. Crea una cuenta en easybits.cloud
  2. Ve al Dashboard de Desarrollador y crea una API key
  3. 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.

ScopeAllows
READList and get files, websites, documents, webhooks, and usage stats
WRITECreate, upload, update, optimize, transform, and share files. Create websites, webhooks, databases, documents, and presentations
DELETESoft-delete and permanently remove files, websites, webhooks, and other resources
ADMINFull 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.

Web clients (Claude.ai / Cowork): use OAuth 2.1 + Dynamic Client Registration instead of an API key. See the Claude Cowork section →

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

  1. Instala el CLI: npm install -g ghostycode (o curl -fsSL https://formmy.app/ghosty/install.sh | sh)
  2. Autentica con tu key de EasyBits (sirve para LLM + MCP): ghosty auth set --provider easybits --api-key "TU_EASYBITS_API_KEY"
  3. 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

⚡EasyBits MCP

293 herramientas: sandboxes, web, archivos, DBs, documentos y hosting

🧠DeepSeek V4

Modelo principal con razonamiento profundo (thinking tokens)

🌐Búsqueda web

BrightData integrado para búsquedas y scraping

🔌MCP dinámico

Agrega y quita servidores MCP en runtime sin reiniciar

📦Sandboxes

Firecracker microVMs para ejecutar código y agentes aislados

🔄Auto-actualización

ghosty update para mantener todo al día

¿Ya usas Ghosty Code? Mantén el binario al día con 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

  1. In Cowork, open Settings → Connectors → Add custom connector
  2. Paste the MCP URL: https://www.easybits.cloud/api/mcp (or pick a toolset: /api/mcp/sandbox)
  3. Click Connect — you'll be redirected to EasyBits to log in
  4. Authorize the connector. You're done — the agent has access to your workspace
Tip: append ?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:

EndpointSpecPurpose
/.well-known/oauth-protected-resource[/<resource path>]RFC 9728Tells 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-serverRFC 8414Advertises authorize, token, and registration endpoints
/oauth/registerRFC 7591Dynamic Client Registration — https or loopback redirects only; public (PKCE) or client_secret_post
/oauth/authorizeOAuth 2.1User consent + code issuance (PKCE S256 required)
/oauth/tokenOAuth 2.1Exchanges code + verifier for a 1-hour JWT access token
/oauth/revokeRFC 7009Revokes the whole grant (refresh + access). Users can also disconnect apps in Developer → Claude.ai

Handshake flow

Flowtypescript

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 mcp scope — the authorized session has full access to the MCP handler.
Deep dive in the OAuth 2.1 + DCR blog post.

SDK

El SDK tipado envuelve toda la REST API. Instálalo y úsalo en cualquier proyecto Node.js/Bun/Deno.

Instalartypescript

Todos los métodos

Archivos

MethodDescription
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

MethodDescription
bulkUploadFiles(items)Sube hasta 20 archivos a la vez
bulkDeleteFiles(fileIds)Borra hasta 100 archivos a la vez

Imágenes

MethodDescription
optimizeImage(params)Convierte a WebP/AVIF
transformImage(params)Redimensiona, rota, voltea, convierte, escala de grises

Compartir

MethodDescription
shareFile(params)Comparte con otro usuario por email
generateShareToken(fileId, expiresIn?)URL de descarga temporal
listShareTokens(params?)Lista tokens (paginado)

Formularios

MethodDescription
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

MethodDescription
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

MethodDescription
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

MethodDescription
getUsageStats()Storage, conteo de archivos, info del plan
listProviders()Proveedores de storage
listKeys()API keys

Manejo de errores

SDKtypescript

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…):

bash

Y pégale este prompt, cambiando lo del final por lo que necesites:

text

El agente te pasará una liga para entrar la primera vez; lo demás lo hace él.

Instalar

bash

Requiere Node 22 o superior.

Login

bash

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

bash

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

bash

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)

bash

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

bash

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

bash

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)

bash

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:

bash

Los de una caja ligan un dominio a un puerto de un sandbox o máquina:

bash

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.

bash

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

bash

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)

bash

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.

bash

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.

bash

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

bash

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

bash

Configurar un agente con máquina (ghosty-lite, goose; mismo contrato que la CLI ghosty de Ghosty Studio). Toda escritura acepta --dry-run:

bash

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

bash

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

bash

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

bash

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

bash

MCP, SSH y docs

bash

SSH por el 443 — agrega a ~/.ssh/config y entra con ssh <nombre>.ghosty:

text

--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ódigoSignifica
0ok
1error de la API (4xx/5xx), o domains verify todavía no listo
2error de uso: comando, subcomando o argumento faltante o desconocido
3sin 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 --json y 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-browser no 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 --json para leer stdout, stderr y exitCode juntos.
  • Borrar con --json exige --yes; sin él sale con 2.
  • Antes de tocar la config de un agente, previsualiza con --dry-run; después, verifica con easybits 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 (incluye easybits-cli).

Archivos

GET/files

Lista tus archivos (paginado)

Query Parameters
assetIdstringFiltra por ID de asset
limitnumberMáx resultados (default 50, máx 100)
cursorstringCursor de paginación
statusstringPon 'DELETED' para listar archivos borrados
Response
SDK
GET/files/:fileId

Obtén los detalles del archivo con una URL de descarga temporal

Response
SDK
POST/files

Crea un registro de archivo y obtén una URL de subida prefirmada

Request Body (JSON)
fileNamestringRequerido
contentTypestringTipo MIME (requerido)
sizenumberTamaño en bytes (requerido, 1B–5GB)
accessstring'public' o 'private' (default)
regionstring'LATAM', 'US' o 'EU'
Response
SDK

Sube los bytes con PUT a putUrl, luego haz PATCH del status del archivo a 'DONE'.

PATCH/files/:fileId

Actualiza nombre, nivel de acceso, metadata o status del archivo

Request Body (JSON)
namestringNuevo nombre
accessstring'public' o 'private'
metadataobjectPares clave-valor (se fusionan, máx 10KB)
statusstringSolo 'DONE' (desde PENDING)
SDK
DELETE/files/:fileId

Borrado suave (retención de 7 días)

Response
SDK
POST/files/:fileId/restore

Restaura un archivo borrado (soft-delete)

Response
SDK
GET/files/search?q=...

Búsqueda de archivos en lenguaje natural con IA (requiere AI key)

Query Parameters
qstringConsulta en lenguaje natural (requerida)
Response
SDK
POST/files/:fileId/duplicate

Crea una copia de un archivo existente (nuevo objeto de storage)

Request Body (JSON)
namestringNombre de la copia (opcional, default 'Copy of ...')
Response
SDK
GET/files/:fileId/permissions

Lista los permisos de compartición de un archivo

Response
SDK

Operaciones en lote

POST/files/bulk-upload

Crea varios registros de archivo y obtén URLs de subida prefirmadas (máx 20)

Request Body (JSON)
itemsarrayArreglo de { fileName, contentType, size, access? }
Response
SDK

Cada archivo se sube con PUT a su putUrl, luego se pone el status en DONE.

POST/files/bulk-delete

Borra varios archivos a la vez (soft-delete, máx 100)

Request Body (JSON)
fileIdsstring[]Arreglo de IDs de archivo a borrar
Response
SDK

Imágenes

GET/stock-photos

Busca una foto libre de regalías en bancos gratuitos (Pexels → Unsplash → Pixabay → Openverse) y devuelve la primera coincidencia

Query Parameters
qstringQué buscar (requerido). En inglés da mejores resultados en todos los bancos
savebooleanGuarda la foto en tu biblioteca y añade fileId + savedUrl. Requiere scope WRITE; buscar sin guardar, no
Response
SDK

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_photoMCP

query, 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.

POST/files/:fileId/optimize

Convierte la imagen a WebP o AVIF (crea un archivo nuevo)

Request Body (JSON)
formatstring'webp' (default) o 'avif'
qualitynumber1–100 (default: 80 webp, 50 avif)
Response
SDK
POST/files/:fileId/transform

Redimensiona, recorta, rota, voltea o convierte una imagen (crea un archivo nuevo)

Request Body (JSON)
widthnumberAncho objetivo en px
heightnumberAlto objetivo en px
fitstring'cover', 'contain', 'fill', 'inside', 'outside'
formatstring'webp', 'avif', 'png', 'jpeg'
qualitynumber1–100
rotatenumberGrados
flipbooleanVoltea vertical
grayscalebooleanConvierte a escala de grises
Response
SDK

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).

Se mide en consultas, no en créditos. 1 consulta = 1 página leída, 1 búsqueda, 1 registro extraído — y en un rastreo, cada página que lee (máx. 20 por llamada). Tienes 50 al registrarte; los packs Web ($99 → 400, $999 → 10,000) están en /dash/packs?tab=web, valen para cualquier plan y no caducan. Sin saldo → 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").
POST/web/search

Busca en Google (o Bing/Yandex/DuckDuckGo) y devuelve resultados estructurados: orgánicos, negocios locales, knowledge panel

Request Body (JSON)
querystringTexto plano (requerido)
enginestringgoogle (default) | bing | yandex | duckduckgo
countrystringPaís en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo
Response

1 consulta. Úsalo para encontrar la URL correcta y luego léela con /web/fetch.

POST/web/fetch

Lee una página aunque bloquee bots. Devuelve HTML o markdown

Request Body (JSON)
urlstringhttps://… (requerido)
countrystringPaís en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo
asMarkdownbooleantrue → markdown en vez de HTML
onlyMainContentbooleanCon asMarkdown: quita nav, footer, iconos y 'skip to content'. Lo normal cuando vas a leer la página
Response

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).

POST/web/extract

Extrae registros con esquema estable de una fuente conocida. Asíncrono: devuelve un job

Request Body (JSON)
sourcestringgoogle_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
datasetIdstringPara fuentes fuera de la lista (catálogo de +1,000)
inputobject | object[]google_maps → [{ keyword, country: 'MX' }] · mercadolibre → { query, page? } · resto → [{ url }]
limitnumberRegistros máximos por input (default 20, máx 200)
Response

Cobra 1 consulta POR REGISTRO devuelto, una sola vez, al recogerlos. Disparar el job no cuesta; un job que falla no cobra.

GET/web/extract/:jobId

Estado de un job de extract; cuando termina trae los registros

Response

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.

POST/web/crawl

Lee una página y sigue sus links internos (mismo dominio) hasta maxPages

Request Body (JSON)
urlstringURL de inicio (requerido)
maxPagesnumber1-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
onlyMainContentbooleanQuita nav, footer e iconos de cada página (recomendado para RAG)
countrystringPaís en 2 letras: mx (México), us, es, ar, co, cl, pe, br. Ver lista completa abajo
Response

1 consulta por página realmente leída. `pending` son los links vistos y no visitados: pásale uno a otra llamada para continuar.

POST/web/map

Todas las URLs de un sitio desde su sitemap (o los links de la portada), sin leerlas. Úsalo antes de leer un sitio grande

Request Body (JSON)
urlstringURL del sitio (requerido)
searchstringSólo URLs que contengan este texto, p. ej. /blog/
limitnumber1-5000, default 1000. Cuesta 1 consulta por documento leído (normalmente 1-3)
Response

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_searchMCP

query, engine?, country?

Busca en Google y devuelve resultados estructurados. 1 consulta.

web_fetchMCP

url, country?, asMarkdown?, onlyMainContent?

Lee una página aunque bloquee bots. 1 consulta.

web_extractMCP

source | datasetId, input, limit?

Extrae registros con esquema (Maps, Mercado Libre, Amazon, Instagram…). Async con jobId; 1 consulta por registro.

web_extract_statusMCP

jobId

Estado/registros de un extract. Gratis mientras corre.

web_crawlMCP

url, maxPages?, country?, onlyMainContent?

Rastrea un sitio siguiendo links internos. 1 consulta por página.

web_crawl_statusMCP

jobId, cursor?

Estado y páginas (por bloques) de un rastreo grande. Gratis.

web_interactMCP

url, actions?, screenshot?

Navegador remoto: clics, escribir, Enter, scroll; devuelve la página final. 5 consultas por sesión.

web_monitor_createMCP

url, everyMinutes?

Vigila una página y avisa por webhook (web.page.changed) cuando cambia. 1 consulta por revisión.

web_monitor_listMCP

Tus monitores con revisiones y cambios detectados.

web_monitor_deleteMCP

monitorId

Deja de vigilar una página.

web_mapMCP

url, 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.

Compartir

POST/files/:fileId/share

Comparte un archivo con otro usuario por email

Request Body (JSON)
targetEmailstringEmail del destinatario (requerido)
canReadbooleanDefault: true
canWritebooleanDefault: false
canDeletebooleanDefault: false
SDK
POST/files/:fileId/share-token

Genera una URL de descarga temporal

Request Body (JSON)
expiresInnumberSegundos (60–604800, default 3600)
Response
SDK
GET/share-tokens

Lista los share tokens (paginado)

Query Parameters
fileIdstringFiltra por archivo
limitnumberMáx resultados
cursorstringCursor de paginación
SDK

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.

Tipos de campo: text, email, tel, textarea, select, date, number, checkbox, radio, file, matrix (cuadrícula filas × columnas). Templates: formal, brutalista, institucional, editorial.
POST/forms

Crea un formulario hospedado standalone. Devuelve la URL pública /f/:slug.

Request Body (JSON)
namestringNombre del formulario (requerido)
fieldsFormField[]Campos: { name, type, label, required?, placeholder?, options?, showIf?, accept?, section? }
themestringTemplate: formal (default) | brutalista | institucional | editorial
slugstringSlug personalizado (opcional; se deriva del nombre)
successMessagestringMensaje al enviar (opcional)
Response
SDK
GET/forms

Lista tus formularios con el conteo de respuestas.

Response
SDK
PATCH/forms/:formId

Actualiza nombre, theme, campos o mensaje de éxito de un formulario.

Request Body (JSON)
namestringNuevo nombre (opcional)
themestringNuevo template (opcional)
fieldsFormField[]Reemplaza los campos (opcional)
successMessagestringNuevo mensaje al enviar (opcional)
SDK
GET/forms/:formId/submissions

Lista las respuestas de un formulario (más recientes primero).

Request Body (JSON)
limitnumberQuery param. Máx 200, default 50.
Response
SDK
Los archivos subidos (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.

Eventos: file.created, file.updated, file.deleted, file.restored, website.created, website.updated, website.deleted, form.submitted, payment.paid, broadcast.sent
GET/webhooks

Lista tus webhooks configurados

Response
SDK
POST/webhooks

Crea un webhook. El secret solo se devuelve al crearlo — guárdalo.

Request Body (JSON)
urlstringURL HTTPS para recibir las notificaciones POST (requerida)
eventsstring[]Eventos a los que suscribirse (requerido)
Response
SDK

Máx 10 webhooks por cuenta. La URL debe usar HTTPS.

GET/webhooks/:webhookId

Obtén los detalles del webhook (sin el secret)

SDK
PATCH/webhooks/:webhookId

Actualiza URL, eventos o status del webhook

Request Body (JSON)
urlstringNueva URL HTTPS
eventsstring[]Nueva lista de eventos
statusstring'ACTIVE' o 'PAUSED'. Reactivar resetea el contador de fallos.
SDK
DELETE/webhooks/:webhookId

Borra un webhook permanentemente

Response
SDK

Verificar firmas

Node.jsjavascript

Formato del payload

JSONjson

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.

Tools MCP: create_payment_link, list_payment_links. Cuando el pago se aprueba, se dispara el webhook payment.paid.

Crear un link de pago

MCP (Claude)typescript

Webhook payment.paid

JSONjson

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.

Tools MCP: 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

MCP (Claude)typescript

Audiencia + newsletter

MCP (Claude)typescript

Sitios web

Cómo funcionan los deploys de sitios:
  1. Crea un sitio — obtienes un id y una URL tipo https://my-site.easybits.cloud
  2. Sube archivos con fileName puesto en sites/{websiteId}/path (ej. sites/{id}/index.html)
  3. Haz PUT de los bytes a cada putUrl, luego pon el status en DONE
  4. Tu sitio está en vivo — el fallback SPA a index.html viene incluido

Ejemplo de deploy

SDKtypescript

Endpoints

GET/websites

Lista tus sitios web estáticos

SDK
POST/websites

Crea un sitio nuevo

Request Body (JSON)
namestringNombre del sitio (requerido)
slugstringOpcional. URL pública /s/<slug>/: a-z, 0-9 y guiones, 3–60, único. 400 slug_invalid / 409 slug_taken
Response
SDK
GET/websites/:websiteId

Obtén los detalles del sitio

SDK
PATCH/websites/:websiteId

Actualiza el nombre, el slug o el status del sitio. Al cambiar el slug, el viejo responde 301 al nuevo

Request Body (JSON)
namestringNuevo nombre
slugstringNuevo slug (mismas reglas que al crear). 400 slug_invalid / 409 slug_taken
statusstringej. 'DEPLOYED'
SDK
DELETE/websites/:websiteId

Borra el sitio y hace soft-delete de todos sus archivos

SDK

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.

GET/documents

Lista todos tus documentos

Response
SDK
GET/documents/:id

Obtén un documento con todos sus datos de páginas/secciones

Response
SDK
POST/documents

Crea un documento nuevo

Request Body (JSON)
namestringNombre del documento (requerido)
promptstringDescripción para la generación con IA
themestringTema: minimal, calido, oceano, noche, bosque, rosa
customColorsobjectPaleta personalizada: { primary, secondary, accent, surface }
Response
SDK
PATCH/documents/:id

Actualiza la metadata del documento (nombre, tema, colores). Usa las tools de página para cambios de contenido.

Request Body (JSON)
namestringNuevo nombre
promptstringPrompt actualizado
themestringNombre del tema
customColorsobjectPaleta de color personalizada
SDK
DELETE/documents/:id

Borra un documento

SDK
POST/documents/:id/deploy

Publica como sitio en vivo en slug.easybits.cloud

Response
SDK
POST/documents/:id/unpublish

Quita el sitio en vivo y vuelve a borrador

SDK

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_htmlMCP

documentId, pageId

Obtén el HTML y la metadata de una sola página.

set_page_htmlMCP

documentId, pageId, html

Actualiza el HTML completo de una página. Preferible a update_document para editar contenido.

get_section_htmlMCP

documentId, pageId, cssSelector

Obtén el outerHTML de un elemento específico dentro de una página por selector CSS.

set_section_htmlMCP

documentId, pageId, cssSelector, html

Reemplaza un elemento específico dentro de una página. Permite ediciones quirúrgicas.

add_pageMCP

documentId, html?, afterPageIndex?, label?

Agrega una página nueva. Opcionalmente pasa el HTML y la posición de inserción.

delete_pageMCP

documentId, pageId

Elimina una página. No se puede borrar la última que queda.

reorder_pagesMCP

documentId, pageIds

Reordena todas las páginas. pageIds debe contener cada ID de página exactamente una vez.

get_page_screenshotMCP

documentId, 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_documentMCP

documentId, 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_sectionMCP

documentId, sectionId, instruction

Cambios quirúrgicos con IA a una página específica. Usa get_page_html para ver el resultado.

regenerate_document_pageMCP

documentId, sectionId

Rediseña una página por completo manteniendo la misma intención de contenido.

enhance_document_promptMCP

name, prompt?, action?

Auto-genera una descripción desde el título o mejora un prompt existente.

get_document_directionsMCP

prompt, pageCount?, sourceContent?

Obtén 4 direcciones de diseño (fuentes, colores, mood). Pasa una a generate_document.

clone_documentMCP

documentId, 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).

GET/video-projects

Lista tus proyectos de video

Response
SDK
POST/video-projects

Crea un proyecto de video (vacío o con escenas)

Request Body (JSON)
namestringNombre del proyecto
formatobjectPreset de aspecto: { preset: 'reel' | 'story' | 'square' | 'landscape' }
themestringFondo: default | dark | light | brand
scenesarrayEscenas iniciales opcionales [{ html, timeline?, durationSec?, narration? }]
Response
SDK
POST/video-projects/:id/scenes

Agrega una escena (markup + animación + narración)

Request Body (JSON)
htmlstringMarkup de la escena (absoluto; assets como assets/<name>)
timelinestringSnippet GSAP contra `tl` (ej. tl.from('#t',{opacity:0,y:40,duration:0.6}))
durationSecnumberDuración; si hay narración, se ajusta para que quepa
narrationstringTexto de voz en off (kokoro em_santa)
Response
SDK
PATCH/video-projects/:id/scenes/:sceneId

Edita una escena. Cambiar narration re-sintetiza la voz en el próximo render.

SDK
PUT/video-projects/:id/audio

Registra un asset (imagen/logo) que la caja baja a assets/; referéncialo en el HTML como assets/<name>

Request Body (JSON)
urlstringURL pública del asset
namestringNombre de archivo, ej. logo.png
SDK
POST/video-projects/:id/audio

Música de fondo continua (auto-duckeada bajo la narración). url: null para quitar.

Request Body (JSON)
urlstringURL pública de audio (o null)
SDK
POST/video-projects/:id/render

Compila, sintetiza la narración pendiente y renderiza a MP4 en el microVM. Síncrono (decenas de segundos).

Response
SDK

MCP tools (12)

create_video_projectMCP

name?, format?, theme?, scenes?

Crea un proyecto de video doc-style.

list_video_projectsMCP

limit?, offset?, status?

Lista proyectos de video.

get_video_projectMCP

projectId

Proyecto con su lista completa de escenas.

update_video_projectMCP

projectId, name?, theme?, fps?, width?, height?

Actualiza metadata (no toca escenas).

delete_video_projectMCP

projectId

Elimina el proyecto.

add_video_sceneMCP

projectId, html, timeline?, durationSec?, narration?, afterIndex?

Agrega una escena.

set_video_sceneMCP

projectId, sceneId, html?, timeline?, durationSec?, narration?

Edita una escena por id.

delete_video_sceneMCP

projectId, sceneId

Elimina una escena.

reorder_video_scenesMCP

projectId, sceneIds

Reordena todas las escenas.

set_video_musicMCP

projectId, url, name?

Música de fondo (o url:null para quitar).

attach_video_assetMCP

projectId, url, name?, type?

Registra imagen/logo como asset.

render_video_projectMCP

projectId

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.

73 herramientas MCP en el grupo sandbox. Agrega --tools sandbox para habilitarlas. Ver tool groups.

Templates

Cada sandbox se crea desde un template. Estos son los disponibles:

TemplateTipoDescripción
ubuntubaseLinux completo. Instalar paquetes, compilar, correr servidores.
pythonbaseRuntime Python; cada run-code es un proceso fresco.
nodebaseNode 24 + typescript, tsx, pnpm, git y python3; cada run-code es un proceso fresco.
bunbaseRuntime Bun.
dev-boxbaseCaja de trabajo limpia (git, curl, build-essential, Node 22); la recomendada para SSH.
code-interpreterbasePython + kernel Jupyter persistente (sandbox_run_cell): variables y gráficas sobreviven entre celdas.
eve-nitrobaseServidor eve (Vercel) self-hosted: Node 24, pnpm, eve CLI 0.65; /data persistente, puerto 3000.
node-agentagenteNode + Claude Agent SDK pre-horneado (agent_run).
claude-codeagenteLoop del Claude Agent SDK; billing por token.
gooseagentegoose (AAIF), agente de código con ACP nativo.
ghostyclawagenteDaemon always-on de Ghosty (WhatsApp, Slack, Telegram) con Docker y admin-api.
ghosty-liteagenteAgente ACP ligero en Rust, multi-provider; el cerebro puede ser tu llave EasyBits.
open-ghostyagenteGhosty sobre modelos abiertos, chat web SSE.
lang-ghostyagenteGhosty sobre LangChain, chat web SSE.
rust-ghostyagenteGhosty DeepSeek-first (CodeWhale/Rust) con web SSE y WhatsApp.
ghosty-gcagenteGhosty para equipos (GTeams): hilos, artefactos, editor colaborativo.
ghosty-chatagenteChat Ghosty persistente (Express + SSE).
cagent-ghostyagenteGhosty sobre cagent (Docker), chat web SSE.
openclawagenteOpenClaw, IA personal always-on.
chat-openaiagenteChat persistente Express+SSE con OpenAI; créalo con agent_create.
chat-anthropicagenteChat persistente Express+SSE con Anthropic; créalo con agent_create.
ghosty-studioagenteGhosty Studio: control plane de agentes dentro de una caja.
desktop-ghostyagenteEscritorio Linux con Ghosty (noVNC).
computer-ghostyagenteComputer-use con escritorio XFCE + noVNC público.
computer-ghosty-geminiagenteComputer-use con Gemini.
livekit-svcservicioSala de videollamada + grabación HD (Studio).
whisper-svcservicioSTT whisper; parte de la caja voice.
kokoro-svcservicioTTS kokoro; parte de la caja voice.
voice-svcservicioVoz (STT + TTS) para la flota; service_start('voice').
render-svcservicioChromium para PDF/PNG/auditoría; service_start('render').
collab-svcservicioEditor colaborativo (Yjs) de GTeams.
hyperframes-svcservicioRender de video HyperFrames.
claude-workerinternoWorker de la flota (Claude). Lo crea la plataforma.
codex-workerinternoWorker de la flota (Codex). Lo crea la plataforma.
gemini-workerinternoWorker 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 & rutaBodyQué hace
GET /sandboxes—Lista tus sandboxes vivas
POST /sandboxestemplate*, timeoutSeconds, name, size, metadata, persistent, suspendOnIdle, hardTtlSecondsCrea una microVM
GET /sandboxes/:id—Estado de una caja
DELETE /sandboxes/:id—La destruye
POST /sandboxes/:id/execcommand*, cwd, env, timeoutSecondsCorre un comando de shell
POST /sandboxes/:id/run-codecode*, lang (python|node|bash), timeoutSecondsEjecuta código en proceso fresco
POST /sandboxes/:id/run-cellcode*, timeoutSecondsCelda en el kernel persistente
POST /sandboxes/:id/kernel-restart—Reinicia el kernel Jupyter
POST /sandboxes/:id/exposeport*URL pública HTTPS del puerto
POST /sandboxes/:id/expose-rawport*, protocol* (tcp|udp)Forward L4 crudo
POST /sandboxes/:id/unexpose-rawport*, protocol*Cierra el forward L4
POST /sandboxes/:id/suspend—Duerme la caja (snapshot)
POST /sandboxes/:id/resume—La despierta
POST /sandboxes/:id/extendextendSecondsAlarga el TTL
POST /sandboxes/:id/snapshotnameCongela el disco en una imagen
POST /sandboxes/:id/forkcount, name, metadata, timeoutSecondsClona N hijos copy-on-write
POST /sandboxes/:id/template-snapshotkey*, hash*, nameCaptura 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-policypolicy* (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/writepath*, content*Escribe un archivo
POST /sandboxes/:id/files/deletepath*Borra
POST /sandboxes/:id/files/movefrom*, to*Mueve o renombra
POST /sandboxes/:id/files/mkdirpath*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.

El snapshot NO detiene la caja — sigue corriendo. El fork la clona; los hijos heredan el disco completo al momento del snapshot y cuentan contra tu límite de sandboxes concurrentes.

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.

Nunca metas credenciales en la plantilla: el host hace scrub de /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.

SDKtypescript

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.

SDKtypescript
  • El hostPort sale 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.

1. En tu máquinatypescript
2. Inyectarla en la cajatypescript
  • 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_disable cierra 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.

Una veztypescript

En ~/.ssh/config:

~/.ssh/configtypescript

Y ya:

Terminaltypescript
  • Sigue haciendo falta ssh-enable una vez, para inyectar la llave: el sshd de la caja es fail-closed. Pásale la salida de easybits ssh-key — es la misma que usa ssh-proxy al 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 ssh y 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).

Flujo (3 pasos):
  1. sandbox_domain_add → te devuelve en dns el registro EXACTO a crear.
  2. 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).
  3. sandbox_domain_verify → confirma que ya resuelve y sirve con TLS. El cert se emite solo en el primer acceso.
SDKtypescript

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.

SDKtypescript

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.

SDKtypescript

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.

SDKtypescript

¿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_resume desde 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.

SDKtypescript
  • 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, no ln -s.
  • Variables disponibles: EB_RESUME=1 y EB_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.

La credencial es POR LLAMADA y no se queda en la caja. El token viaja para esa operación y se borra al terminar: nunca se escribe en el .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_status devuelve datos, no texto: { branch, upstream, ahead, behind, clean, staged[], modified[], untracked[], conflicted[] }. Sale de porcelain=v2, así que no cambia entre versiones de git ni con el idioma del sistema.
  • sandbox_git_commit sin cambios devuelve { nothingToCommit: true } como éxito. Un error ahí invita al agente a reintentar, y reintentar no cambia nada: es un bucle.
  • sandbox_git_checkout con create: true usa -B, idempotente — pensado para correrse en cada arranque sin fallar con "branch already exists".
  • sandbox_git_push con force usa --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, default EasyBits Agent), sin dejar un git config escrito 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_createMCP

template, timeoutSeconds

Crear un sandbox nuevo

sandbox_listMCP

—

Listar sandboxes activos

sandbox_statusMCP

sandboxId

Estado del sandbox (running/stopped/error)

sandbox_destroyMCP

sandboxId

Destruir y liberar recursos

sandbox_extendMCP

sandboxId, extendSeconds

Extender TTL del sandbox

sandbox_suspendMCP

sandboxId

Snapshot a disco y liberar CPU (pausa el TTL)

sandbox_resumeMCP

sandboxId

Restaurar desde snapshot (restaura el TTL restante)

sandbox_set_idleMCP

sandboxId, suspendOnIdle, idleTtlSeconds

Siesta en una caja YA creada: al vencer el TTL se suspende en vez de destruirse

sandbox_execMCP

sandboxId, command

Ejecutar comando (sync, 60s por defecto, tope 600s)

sandbox_exec_backgroundMCP

sandboxId, command

Ejecutar comando en background

sandbox_exec_listMCP

sandboxId

Listar procesos en background (recupera un execId perdido)

sandbox_exec_statusMCP

sandboxId, execId

Consultar estado de ejecución background

sandbox_exec_killMCP

sandboxId, execId

Matar una ejecución background (lo que falta cuando algo se cuelga)

sandbox_run_codeMCP

sandboxId, code, lang

Ejecutar Python/Node/Bash inline

sandbox_run_cellMCP

sandboxId, code

Ejecutar celda en kernel Jupyter persistente

sandbox_set_bootstrapMCP

sandboxId, script, mode?, timeoutSeconds?

Receta que el host corre en CADA despertar (idempotente)

sandbox_git_cloneMCP

sandboxId, repo, dir, branch?, depth?, commit?, token?

Clonar un repo dentro de la caja (token por llamada)

sandbox_git_statusMCP

sandboxId, dir

Estado como datos: branch, ahead, behind, clean, archivos

sandbox_git_commitMCP

sandboxId, dir, message, addAll?, paths?

Commit (sin cambios → nothingToCommit, es éxito)

sandbox_git_pushMCP

sandboxId, dir, branch?, setUpstream?, token?

Publicar (force usa --force-with-lease)

sandbox_git_pullMCP

sandboxId, dir, rebase?, token?

Traer cambios del remoto

sandbox_git_checkoutMCP

sandboxId, dir, branch, create?, from?

Cambiar de rama (create usa -B, idempotente)

sandbox_git_logMCP

sandboxId, dir, limit?, cursor?

Historial paginado

sandbox_files_writeMCP

sandboxId, path, content

Escribir archivo en el sandbox

sandbox_files_readMCP

sandboxId, path

Leer archivo del sandbox

sandbox_files_listMCP

sandboxId, path

Listar directorio

sandbox_files_editMCP

sandboxId, path, oldString, newString

Edición quirúrgica in-place (sin escaping de shell)

sandbox_logsMCP

sandboxId, unit?, lines?, since?, grep?

Logs journald nativos del daemon

sandbox_runtimeMCP

sandboxId, action, unit?, buildCommand?

systemd status/restart/rebuild del daemon

sandbox_apply_patchMCP

sandboxId, edits[], rebuild?, restart?

Hotfix atómico: edita → rebuild → restart

sandbox_adminMCP

sandboxId, path, method?, body?

Pasarela al admin API interno (:8787) de una máquina permanente

sandbox_expose_portMCP

sandboxId, port

Exponer puerto como URL pública HTTPS (solo HTTP)

sandbox_expose_raw_portMCP

sandboxId, port, protocol

Forward TCP/UDP crudo; devuelve endpoint host:hostPort

sandbox_unexpose_raw_portMCP

sandboxId, port, protocol

Cerrar el forward TCP/UDP

sandbox_ssh_enableMCP

sandboxId, publicKeys[]

SSH a la caja: inyecta llave, abre el 22, devuelve el comando

sandbox_ssh_disableMCP

sandboxId

Cerrar el puerto SSH (no revoca la llave)

sandbox_domain_addMCP

sandboxId, domain, port

Atar dominio propio (devuelve el registro DNS: CNAME o A)

sandbox_domain_removeMCP

sandboxId, domain

Quitar dominio personalizado

sandbox_domain_listMCP

sandboxId

Listar dominios del sandbox

sandbox_domain_verifyMCP

domain

Confirmar DNS + cert TLS del dominio

agent_createMCP

template

Crear agente persistente (endpoint HTTP)

agent_listMCP

—

Listar agentes persistentes

agent_messageMCP

agentId, content

Enviar mensaje a un agente

fleet_agent_listMCP

—

Grupo fleet · listar los agentes de la flota (WhatsApp/web/Teams)

fleet_agent_capabilitiesMCP

fleetAgentId

Grupo fleet · config actual de un agente (= GET /capabilities)

fleet_agent_configureMCP

fleetAgentId, action, params?

Grupo fleet · aplicar una acción de /capabilities

agent_runMCP

prompt, model?

Agente Claude one-shot (async)

agent_run_statusMCP

jobId

Consultar estado de agent_run

templates_listMCP

tier?

Listar templates disponibles

Rate limits: 10 spawns/min (sandbox_create, agent_create, agent_run). 120 operaciones/min para el resto. Sandboxes se auto-destruyen al TTL (default 5 min; máx según plan: Byte 1h · Mega 4h · Tera 24h).

Skill para tu agente: npx skills add https://easybits.cloud instala easybits-sandbox (la API de cajas) y easybits-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.

json

Los errores de una caja traen { "error": "<código>", "message": "…" }:

StatuserrorQué pasóQué hacer
403AgentLimitReachedTu plan ya tiene todas sus cajas persistentes (agentes ACP o de marca), vivas o dormidas: Byte 1, Mega 2, Tera 5Borra un agente (existing trae cuáles) o sube de plan (upgradeUrl)
409SandboxBusyHay un snapshot o fork en curso sobre la cajaConsulta activity con GET /sandboxes/:id y reintenta cuando desaparezca (suelen ser minutos)
409SandboxNotReadyLa caja aún está en starting (o dormida)Espera status=running, o resume si está suspended
409SandboxUnreachableEl host no alcanza al agente dentro de la caja (reiniciando o colgada)Espera y reintenta; si sigue, destrúyela y crea otra
504SandboxHostTimeoutEl host no respondió a tiempo; la operación puede seguir en cursoRevisa GET /sandboxes/:id y reintenta en unos minutos; no la repitas a ciegas
502SandboxHostErrorEl 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.

El inicio de sesión es tuyo. Lo haces tú, con tu cuenta, en la página de OpenAI. Easybits nunca ve tu contraseña: sólo guarda la sesión que deja Codex y la usa en tus cajas. Es el caso «máquina remota» que OpenAI documenta para Codex. Los agentes de la Flota con el motor 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:

bashbash

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 connect nunca reemplaza una sesión que la caja ya tenga (con --force sí).
  • 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.json con 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.

Una sola llave. Tu 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):

bashbash

Y ahora sí, el comando tal cual:

bashbash
Respuesta

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:

bashbash

Y consultas su estado:

bashbash
Respuesta

3. Conectar tu cliente

bashbash

La URL del paso 2 más el token del paso 1. Sin token, o con uno equivocado, responde 401.

4. Ver tu saldo

curlbash
Respuesta
Dos cosas que parecen "no funciona" y no lo son: la URL 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.

bashbash

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.

RutaMétodosQué hace
/files/<ruta>GET · PUT · DELETEArchivos de conocimiento en /data/work. PUT con los bytes crudos (máx 10 MB). Sin reinicio.
/skills/<slug>PUT · DELETEJSON { markdown, assets?: [{ name, contentBase64 }] }. Entra tras restart. GET /skills las lista.
/mcpGET · PUTPUT { servers: [...] } reemplaza la lista entera y reinicia solo.
/restartPOSTRearranca el agente y abre sesión nueva (se pierde la conversación en curso).
/promptGET · PATCH /agents/:idIdentidad (ver arriba).
bashbash

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.

bashbash

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.

Los MCP se montan al crear la sesión, no en cada turno. Si cambias el código de un MCP que vive en el disco de la caja, el agente sigue con el de antes hasta que la sesión se rehaga. Para probar un cambio, crea otro agente. Y si la caja se pierde y EasyBits la reconstruye, tus 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).

POST/api/v2/agents

Crea el agente. Devuelve agentId y embedToken; su agentUrl es provisional todavía.

Request Body (JSON)
templatestring"ghosty-lite" (requerido)
namestringCómo lo verás en tu lista de agentes
envobjectVacío para el cerebro medido con tu llave. Ver Otro cerebro.
mcpServersarrayTus propias tools MCP (stdio o http/sse). Ver #ghosty-lite-mcp.
GET/api/v2/agents/:agentId

Estado del agente. Cuando status es running, agentUrl trae la URL WebSocket estable.

Response

Hablarle por HTTP

curlbash
POST/api/v2/agents/:agentId/message

Un turno de conversación. Devuelve SSE. Sirve la eb_sk del dueño o el embedToken desde el navegador.

Request Body (JSON)
contentstringEl mensaje del usuario (requerido)
sessionIdstringHilo de conversación (default 'default'). Un id por visitante en embeds multi-usuario.
Response

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.

bashbash

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.

CerebroenvConsumo
easybits{} (default)Tus tokens de EasyBits
anthropicGHOSTY_PROVIDER, GHOSTY_MODEL, ANTHROPIC_API_KEYTu cuenta de Anthropic. API key normal — un token OAuth (sk-ant-oat…) no sirve: este proveedor autentica con x-api-key.
custom_deepseekDEEPSEEK_API_KEYTu cuenta de DeepSeek
openai · openrouter · google · ollamaGHOSTY_PROVIDER + su *_API_KEYTu proveedor
Todavía no: las herramientas de EasyBits (archivos, documentos) aún no llegan al modelo en este template — el agente trae sus propias herramientas locales (shell, editar archivos, analizar código) y el cerebro medido. Si tu caso necesita que el agente toque tu cuenta de EasyBits, usa por ahora el template 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_createMCP

template: "ghosty-lite", env

Crear el agente (mismo flujo que el POST)

agent_messageMCP

agentId, content

Un turno; devuelve { content, tokens, usage? }

agent_listMCP

—

Listar tus agentes

agent_destroyMCP

agentId

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.

¿Prefieres UI? Administra tu flota desde el dashboard en /dash/flota — crear agentes, vincular WhatsApp, prender/apagar grupos y desconectar.
¿Construyes sobre EasyBits? Crea y configura agentes por código con el SDK (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)

  1. Entra a /dash/flota y crea un agente.
  2. 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.
  3. 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.

  1. En /dash/flota, sobre tu agente, pulsa Conectar WhatsApp Business. Se abre el wizard de Meta (Embedded Signup) en un popup.
  2. Sigue los pasos de Meta para vincular tu cuenta de WhatsApp Business. Al terminar, el número queda asociado a ese agente.
  3. Un número recién conectado arranca apagado. Elige su estado de respuesta en Conversaciones para que empiece a atender.
Coexistencia: puedes conectar el mismo número que ya usas en la app de WhatsApp. La coexistencia siempre está activa: el agente atiende mientras tú no estás, y en cuanto respondes una conversación desde tu teléfono el bot se pausa solo en esa conversación (handoff humano). Desde el Inbox lo reactivas o lo pausas tú con un botón.

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.

Compra cajas para tu flota en /dash/packs — elige cuántas cajas necesitas y se suman a tu capacidad al instante.

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.

El token del agente es de servidor. Autoriza TODO lo de esta página, incluido reconfigurarlo y borrarlo. Llama a estos endpoints desde tu backend y proxea la respuesta a tu front — nunca lo pongas en el browser.

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.

Manda siempre 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.

ScopePuedeNo puede
MESSAGEMandar turnos (/message, /message-stream).Nada de configuración.
MANAGETodo lo anterior + leer y ajustar config: prompt, modelo, effort, canales, capacidades.Secretos, MCPs, skills, motor, borrar.
ADMINTodo, incluidos set-secret, add-mcp, set-engine y borrar el agente.—
Dos prefijos, dos posturas. 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.
Por qué 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)SDKQué haceAlcanceScope mín.
set-agent-promptsetAgentPromptEl prompt base: quién es el agente.Todo el agenteMANAGE
set-modelsetModelModelo del motor.Todo el agenteMANAGE
set-effortsetEffortCuánto piensa: low · medium · high · xhigh.Todo el agenteMANAGE
add-mcpaddMcpConecta 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 agenteADMIN
remove-mcpremoveMcpLo quita del catálogo.Todo el agenteADMIN
recycle-boxrecycleBoxRecicla 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 agenteADMIN
set-enginesetEngineCambia el motor (Claude, DeepSeek, Codex…). Recicla las cajas solo.Todo el agenteADMIN
set-namesetNameNombre del agente.Todo el agenteMANAGE
toggle-own-numbertoggleOwnNumberNúmero dedicado: sin prefijo Nombre: en WhatsApp.Todo el agenteMANAGE
add-skill · toggle-skill · delete-skilladdSkill · toggleSkill · deleteSkillSkills (SKILL.md + scripts subidos a Files como fileIds).Todo el agenteMANAGE · ADMIN para add-skill · delete-skill
connect-teamsconnectTeamsMarca el canal Teams como conectado.Todo el agenteMANAGE
toggle-assettoggleAssetArchivo del owner adjunto como contexto del canal.Por canalMANAGE
set-db-allowsetDbAllowNamespaces de DB que el agente puede tocar ([] = todas).Por canalMANAGE
set-secretsetSecretGuarda la credencial que usa un MCP (cifrada, no se vuelve a leer).Todo el agenteADMIN
set-promptsetGroupPromptPrompt que se suma al base, sólo en este canal.Por canalMANAGE
set-toolgroupsetToolGroupQué tools de EasyBits ve: buckets (imágenes, documentos, investigación…).Por canalMANAGE
set-cap-levelsetCapLevelNivel de una capacidad: off · read · write.Por canalMANAGE
set-tool-denysetToolDenyProhíbe una tool concreta.Por canalMANAGE
toggle-builtintoggleBuiltinPrende/apaga un conector incluido.Por canalMANAGE

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:

  1. Base del agente — set-agent-prompt. Quién es, en todos los canales.
  2. Del canal — set-prompt con groupId. Cómo se comporta en tu app.
  3. Del turno — appendSystemPrompt en el propio mensaje. Quién pregunta, qué plan tiene, qué está viendo. Sin escribir nada en la config.
La capa 1 se hornea al arrancar la caja del agente: cambiarla aplica a conversaciones nuevas, no a una que ya está viva. Las capas 2 y 3 son inmediatas. Para contexto que cambia por usuario o por pantalla, usa la 3.
Por MCP todavía no. Toda esta superficie existe en SDK y REST, pero aún no hay herramientas MCP para crear o configurar un agente de la flota — las 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í.

agent/sandbox.tstypescript

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).

5 herramientas MCP en el grupo hosting. Agrega --tools hosting para habilitarlas. Requiere plan de pago (Mega/Tera) — el plan es el gate de acceso.
¿Prefieres UI? Administra tus sandboxes desde el dashboard en /dash/hosting — crear, ver estado, promover un sandbox a permanente, y liberar.

Catálogo de tiers

TiervCPU / RAM / NVMeSharedReserved
nano1 / 256MB / 1GB$49—
micro1 / 1GB / 2GB$99—
estandar2 / 2GB / 16GB$449—
focus4 / 8GB / 32GB$690$1,725
performance8 / 16GB / 64GB$1,290$3,225
performance-4x16 / 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.

Aprendido con una app real (React Router v7 + Express 5): 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.

Cobro: el plan da acceso; cada sandbox factura aparte (flat MXN/mes, prorrateado). 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.

El runner de GitHub es Linux x64, igual que la microVM, así que los módulos nativos compilan para el destino correcto. Construir en una Mac sí rompe: un 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 tarea key, name, path, cron, schedule (en palabras), status (al-dia · tarde · fallo · pausado · nuevo), paused, running, nextRunAt, lastRun y recent (ú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 que GET /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 }) y pause_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.

Límite por plan: Byte 3 · Mega 10 · Tera 20 bases de datos. El nombre admite letras, números, guiones y guiones bajos (máx 64 caracteres).

Crear y consultar

Herramientas MCP del grupo databases

db_listMCP

—

Listar tus bases de datos

db_createMCP

name, description?

Crear una base de datos aislada

db_getMCP

dbId

Obtener una base de datos

db_deleteMCP

dbId

Eliminar la base de datos y todos sus datos (irreversible)

db_queryMCP

dbId, sql, args?

Ejecutar una consulta SQL

db_execMCP

dbId, statements

Batch de hasta 20 sentencias

db_importMCP

dbId, table, columns, rows, onConflict?

Importar hasta 10,000 filas de una vez

Eventos de webhook: 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.cloud instala easybits-cli: easybits db create|query|tables|ls|rm con --json.

Errores

Los errores de consulta traen { "error": "…", "code": "<código>" }:

StatuscodeQué pasóQué hacer
400SQL_ERRORLa sentencia falló (sintaxis, tabla inexistente, constraint); error trae el mensaje de SQLiteCorrige el SQL
409DATABASE_STORAGE_MISSINGLa base existe en tu cuenta pero su almacenamiento no está en el servidor; sus datos no están disponiblesBórrala y crea otra, o contacta a soporte
502DATABASE_BACKEND_ERROREl servidor de bases fallóReintenta en un momento
404—La base no existe o no es tuyaRevisa 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.

Solo disponible vía MCP (no hay endpoint REST ni método SDK, por seguridad). Los nombres deben ser estilo variable de entorno: [A-Z_][A-Z0-9_]* (mayúsculas, dígitos y guiones bajos).

Herramientas MCP del grupo secrets

secret_setMCP

name, 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_deleteMCP

name

Eliminar un secreto por nombre

MCPtypescript

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.

La base URL lleva prefijo /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 & rutaQué hace
POST /api/v2/llm/v1/chat/completionsCompletions. Soporta stream: true (SSE)
GET /api/v2/llm/v1/modelsModelos disponibles (proxy al proveedor, caché 5 min)
GET /api/v2/llm/balanceSaldo: usado, restante, plan, recargas, fecha de reset
POST /api/v2/llm/rechargeCompra tokens extra

Notas

  • Modelos: no hardcodeamos la lista — pégale a /models. Si omites model, el default es deepseek-chat.
  • La llave necesita scope WRITE: gastar tokens no es una operación de lectura. Una llave de solo lectura recibe 403 permission_error.
  • Se cobra prompt_tokens + completion_tokens de cada respuesta contra tu saldo. Sin saldo: 402 insufficient_quota, con used y limit en meta.
  • Headers de respuesta: x-llm-tokens-remaining y x-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.

7 herramientas MCP en el grupo 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.

Privacidad: cada sala corre en su propia microVM aislada con llaves generadas por instancia. Los participantes eligen entrar con cámara/mic apagados (el dispositivo se suelta de verdad, sin parpadeo). Si no llamas call_destroy, la sala se apaga sola al TTL de 3 horas.

Cuenta & Uso

GET/usage

Obtén las estadísticas de uso de la cuenta: storage, conteo de archivos, info del plan

Response
SDK
GET/providers

Lista tus proveedores de storage configurados

Response
SDK
GET/keys

Lista tus API keys (solo con auth de sesión)

SDK

Errores & Límites

StatusSignificado
400Petición inválida (params incorrectos)
401No autorizado (API key faltante/inválida)
403Prohibido (scope insuficiente)
404Recurso no encontrado
429Rate limited (demasiadas peticiones)
500Error 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.

GrupoHerramientasDescripción
design95Canva-like: documentos como diseño universal (cualquier formato — letter, social, slide 16:9), brand kits e imágenes. Ideal para Claude.ai / Claude Design.
web15Internet 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).
core114Lo esencial: archivos, DBs, documentos, forms, websites, brand. Para agentes generalistas.
ghosty29Curado 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.
ghostyapp10Para 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).
magnet20Toolset orquestado para crear lead magnets (PDF + landing + form).
video23Video 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.
sandbox81MicroVMs 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.
fleet3Agentes 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.
hosting35Hosting 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.
payments2Links 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.
email7Email transaccional + audiencia + newsletters one-shot. send_email, contactos con tags, y broadcasts con unsubscribe automático. La capa de nurturing del funnel.
public-safe3Subset 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.
claude85La 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í.
scripting3Superficie 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'.
all293Todas las tools disponibles (~50+). Ideal para agentes avanzados.

Uso