## SDK Method Reference

| Method | Description |
|--------|-------------|
| `listFiles(params?)` | List files with pagination |
| `getFile(fileId)` | Get file with download URL |
| `uploadFile(params)` | Create file + get upload URL |
| `updateFile(fileId, params)` | Update name, access, metadata |
| `deleteFile(fileId)` | Soft-delete a file |
| `restoreFile(fileId)` | Restore from trash |
| `listDeletedFiles(params?)` | List trashed files |
| `duplicateFile(fileId, name?)` | Copy a file |
| `searchFiles(query)` | AI-powered search |
| `searchStockPhoto(params)` | Royalty-free stock photo search (1 credit; show `attribution`) |
| `bulkUploadFiles(items)` | Upload up to 20 files |
| `bulkDeleteFiles(fileIds)` | Delete up to 100 files |
| `optimizeImage(params)` | Convert to WebP/AVIF |
| `transformImage(params)` | Resize, rotate, flip, convert |
| `shareFile(params)` | Share with another user |
| `generateShareToken(fileId, expiresIn?)` | Create presigned URL |
| `listShareTokens(params?)` | List share tokens |
| `revokeShareToken(tokenId)` | Revoke a share token |
| `listPermissions(fileId)` | List file permissions |
| `revokePermission(permissionId)` | Revoke a permission |
| `listWebsites()` | List websites |
| `createWebsite(name)` | Create a website |
| `getWebsite(websiteId)` | Get website details |
| `updateWebsite(websiteId, params)` | Update website |
| `deleteWebsite(websiteId)` | Delete website + files |
| `listWebsiteFiles(websiteId, params?)` | List files in a website |
| `listWebhooks()` | List webhooks |
| `createWebhook(params)` | Create webhook |
| `getWebhook(webhookId)` | Get webhook |
| `updateWebhook(webhookId, params)` | Update webhook |
| `deleteWebhook(webhookId)` | Delete webhook |
| `getUsageStats()` | Account usage stats |
| `listProviders()` | Storage providers |
| `listKeys()` | API keys |
| `listPresentations()` | List presentations |
| `getPresentation(id)` | Get presentation with slides |
| `createPresentation(params)` | Create presentation |
| `updatePresentation(id, params)` | Update presentation |
| `deletePresentation(id)` | Delete presentation |
| `deployPresentation(id)` | Publish as live website |
| `unpublishPresentation(id)` | Unpublish presentation |
| `listDatabases()` | List databases |
| `createDatabase(params)` | Create database |
| `getDatabase(dbId)` | Get database |
| `deleteDatabase(dbId)` | Delete database |
| `db(name).query(sql, args?)` | Query a database |
| `listDocuments()` | List documents |
| `getDocument(id)` | Get document with pages |
| `createDocument(params)` | Create document |
| `updateDocument(id, params)` | Update document |
| `deleteDocument(id)` | Delete document |
| `deployDocument(id)` | Publish as live website |
| `unpublishDocument(id)` | Unpublish document |
| `listVideoProjects(params?)` | List video projects |
| `getVideoProject(id)` | Get project with scenes |
| `createVideoProject(params)` | Create video project |
| `addVideoScene(id, scene)` | Add a scene |
| `setVideoScene(id, sceneId, patch)` | Edit a scene |
| `reorderVideoScenes(id, ids)` | Reorder scenes |
| `setVideoMusic(id, url, name?)` | Set/clear background music |
| `renderVideoProject(id)` | Render to MP4 |
| `getDocs(section?)` | Get this documentation |

**Hosting — máquinas always-on** (`eb.machines.*`; la VM además se opera por `sb.*`)

| Method | Description |
|--------|-------------|
| `machines.tiers()` | Catálogo de tiers + precio del disco add-on |
| `machines.list()` | Tus máquinas permanentes |
| `machines.logs(id, { lines?, grep? })` | El log de la app (unit o archivo del startCommand) |
| `machines.launch({ repo \| archiveUrl \| sandboxId, domain? })` | **Una app en producción en UNA llamada** (el `fly launch` de EasyBits). Empieza por aquí. |
| `machines.create({ tier })` | Provisiona una máquina always-on (caja vacía). Lanza `MachinePaymentRequired` con `.checkoutUrl` si la cuenta no tiene plan |
| `machines.buy({ tier })` | Igual pero sin lanzar: devuelve la máquina **o** `{ checkoutUrl }` para que el cliente pague |
| `machines.setRunspec(id, spec)` | Declara appDir/build/start/**dataPaths** — obligatorio antes del primer deploy |
| `machines.runspec(id)` | Lee el runspec |
| `machines.deploy(id, { message? })` | Publica el código actual como release |
| `machines.releases(id)` | Lista releases (versión más nueva primero) |
| `machines.rollback(id, releaseId)` | Vuelve a un release anterior, MISMA caja |
| `machines.redeploy(releaseId, { tier?, replaceSandboxId? })` | Caja NUEVA desde un release: recuperación **y** cambio de tier |
| `machines.backups(id)` | Backups diarios de datos (7 días, incluidos) |
| `machines.backup(id)` | Toma un backup ahora |
| `sb.exec(cmd)` | Corre un comando dentro de la caja |
| `sb.exposePort(port)` | URL pública `sb-<id>-<port>.sandboxes.easybits.cloud` con TLS (HTTP + WebSocket `wss://`) |
| `sb.exposeRawPort(port, proto)` | Forward TCP/UDP crudo; devuelve `endpoint` `host:hostPort` |
| `sb.enableSsh(keys)` | SSH a la caja: inyecta llave + abre el 22; devuelve el comando `ssh` |
| `sb.addDomain(domain, port)` | Dominio propio + TLS automático; devuelve el registro DNS exacto (apex → A, subdominio → CNAME) |
| `sb.verifyDomain(domain)` | Confirma que el dominio ya resuelve y sirve por HTTPS |
| `sb.makePermanent(tier)` | Promueve un sandbox efímero (mismo sandboxId) |
| `sb.release()` | Libera la máquina (corta cobro, 7 días de gracia) |

Una app en producción, con dominio, en una llamada:
```ts
const app = await eb.machines.launch({
  repo: "https://github.com/cliente/tienda",   // o archiveUrl, o sandboxId
  tier: "micro",
  port: 3000,
  dataPaths: ["data", "uploads"],              // sin esto NO hay backup
  domain: "tienda.com",
});
console.log(app.url);          // ya sirve por HTTPS
console.log(app.domain?.dns);  // el registro EXACTO que el cliente debe crear
console.log(app.releaseId);    // ya es recuperable
```

¿Sin repo, subiendo desde su compu? Empaqueta la carpeta, súbela y pasa la URL:
```ts
// tar czf app.tar.gz -C ./mi-tienda .
const up = await eb.uploadFile({ name: "app.tar.gz", access: "private" });
await fetch(up.uploadUrl, { method: "PUT", body: fs.readFileSync("app.tar.gz") });
const app = await eb.machines.launch({ archiveUrl: up.url, domain: "tienda.com" });
```

Si prefieres el control paso a paso (o ya tienes la caja armada), el camino largo sigue disponible: `create` → `exec` → `setRunspec` → `deploy` → `exposePort` → `addDomain`. Solo no olvides el `deploy`.
