The official TypeScript/JavaScript SDK for MIOSA. Works with Node.js 18+, Deno, Bun, and modern bundlers. Fully typed - no any.
Install
What’s new in 1.1.0
| Area | What shipped |
|---|---|
| Sandbox lifecycle | slug, externalUserId, externalProjectId on create; sandbox.update(); sandbox.previewToken() |
| Tenant preview domain | client.tenant.previewDomain.get/set/verify/delete() |
| Tenant branding | client.tenant.branding.get/set/delete() |
| Webhooks | client.webhooks.create/list/get/delete/test() |
| Files advanced | sandbox.files.tree(), sandbox.files.writeMany(), sandbox.files.watch() |
| Sandbox env | sandbox.env.list/set/delete() |
| Processes | sandbox.processes.start/list/get/stop/logs/stream() |
| Templates + fork | client.sandboxTemplates.*, sandbox.fork() |
| Usage / quotas | client.usage.get(), client.quotas.get/set/delete() |
| Audit log | client.auditLog.list() |
| Tenant events (SSE) | client.events.stream({ tenantWide: true }) |
| Sharing | sandbox.share.create/list/revoke() |
| Workspace members | client.workspaceMembers.*, client.workspaceInvites.* |
| Org invites | client.orgInvites.* |
First request
import { Miosa } from "@miosa/sdk";
const client = new Miosa({ apiKey: "msk_live_..." });
// Create a sandbox, run a command, destroy it
const sandbox = await client.sandboxes.create({ templateId: "miosa-sandbox" });
const result = await sandbox.exec.run("python3 -c 'print(1 + 1)'");
console.log(result.stdout); // "2\n"
await sandbox.destroy(); Configure
| Option | Env var | Default | Description |
|---|---|---|---|
apiKey | MIOSA_API_KEY | - | Workspace key (msk_live_*) |
baseUrl | MIOSA_BASE_URL | https://api.miosa.ai/api/v1 | API endpoint |
timeout | - | 30000 | Per-request timeout in milliseconds |
maxRetries | - | 3 | Automatic retries on 429 / 5xx |
import { Miosa } from "@miosa/sdk";
// Reads MIOSA_API_KEY from process.env
const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });
// Explicit configuration
const client2 = new Miosa({
apiKey: "msk_live_...",
baseUrl: "https://api.miosa.ai/api/v1",
timeout: 60_000,
maxRetries: 3,
}); Authentication
The API key is sent as Authorization: Bearer <key> on every request.
import { Miosa } from "@miosa/sdk";
// Standard - key from environment
const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });
// With additional default headers (e.g., for white-label proxies)
const client2 = new Miosa({
apiKey: "msk_live_...",
defaultHeaders: { "X-Tenant-ID": "acme-corp" },
}); Error handling
import {
Miosa,
MiosaError,
AuthenticationError,
NotFoundError,
PermissionError,
RateLimitError,
ValidationError,
InsufficientCreditsError,
ServerError,
} from "@miosa/sdk";
const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });
try {
await client.computers.get("nonexistent-id");
} catch (err) {
if (err instanceof NotFoundError) {
console.log("Computer not found");
} else if (err instanceof AuthenticationError) {
console.log("Invalid API key");
} else if (err instanceof RateLimitError) {
console.log(`Rate limited - retry after ${err.retryAfter}s`);
} else if (err instanceof MiosaError) {
console.log(`API error ${err.statusCode}: ${err.message}`);
}
} Streaming events
sandbox.events.stream() returns an AsyncIterableIterator:
for await (const event of sandbox.events.stream()) {
const type = event["type"] as string | undefined;
console.log(type, event["data"]);
if (["build.completed", "build.failed"].includes(type ?? "")) break;
} Phase 1-5 API Reference
Sandbox lifecycle (Phase 1)
import { randomUUID } from "crypto";
import type { SandboxCreateParams } from "@miosa/sdk";
// Create - full signature
const sandbox = await client.sandboxes.create({
templateId: "miosa-sandbox", // template image
name: "my-sandbox", // optional display name
slug: "acme-backend", // optional URL-safe identifier
externalUserId: "usr_123", // white-label user attribution
externalProjectId: "proj_456", // white-label project attribution
externalWorkspaceId: "ws_789", // white-label workspace attribution
metadata: { env: "staging" }, // arbitrary key-value pairs
timeoutSec: 300, // sandbox lifetime in seconds
alwaysOn: false, // keep alive past idle timeout
idempotencyKey: randomUUID(), // safe for retries
});
const found = await client.sandboxes.get(sandboxId);
const list = await client.sandboxes.list({ state: "running" });
await client.sandboxes.destroy(sandboxId);
// Update mutable fields
await sandbox.update({
name: "updated-name",
slug: "new-slug",
timeoutSec: 600,
alwaysOn: true,
metadata: { version: "2" },
});
// Mint a short-lived preview token
const tokenInfo = await sandbox.previewToken({ expiresIn: 3600 });
// Returns: { token: "mp_...", url: "https://...", expiresAt: "..." }
// Fork from a snapshot or running sandbox
const forked = await sandbox.fork({ snapshotId: "snap_abc", name: "forked" }); | Parameter | Type | Description |
|---|---|---|
name | string | Display name |
slug | string | URL-safe identifier |
externalUserId | string | White-label user attribution |
externalProjectId | string | White-label project attribution |
externalWorkspaceId | string | White-label workspace attribution |
metadata | Record<string, unknown> | Arbitrary key-value pairs |
timeoutSec | number | Lifetime in seconds |
alwaysOn | boolean | Stay alive past idle timeout |
idempotencyKey | string | Deduplication key for retries |
Tenant preview domain (Phase 1)
client.tenant.previewDomain
// Get current custom preview domain
const info = await client.tenant.previewDomain.get();
// { domain: "preview.acme.com", verifiedAt: "...", cnameTarget: "..." }
// Set custom preview domain
await client.tenant.previewDomain.set("preview.acme.com");
// Trigger DNS verification
const result = await client.tenant.previewDomain.verify();
// { verified: true, target: "...", records: [...] }
// Remove custom domain
await client.tenant.previewDomain.delete(); Tenant branding (Phase 1)
client.tenant.branding
// Get current branding
const branding = await client.tenant.branding.get();
// Set branding
await client.tenant.branding.set({
productName: "Acme AI",
logoUrl: "https://cdn.acme.com/logo.png",
supportUrl: "https://acme.com/support",
supportEmail: "help@acme.com",
primaryColor: "#ff6600",
backgroundColor: "#ffffff",
});
// Reset to platform defaults
await client.tenant.branding.delete(); Webhooks (Phase 1)
client.webhooks
// Create a webhook
const wh = await client.webhooks.create({
url: "https://example.com/webhooks/miosa",
events: ["sandbox.created", "sandbox.ready", "sandbox.destroyed"],
});
const list = await client.webhooks.list();
const one = await client.webhooks.get(wh.id);
await client.webhooks.delete(wh.id);
// Send a test ping to verify the endpoint is reachable
await client.webhooks.test(wh.id); Supported events: sandbox.created, sandbox.ready, sandbox.destroyed, sandbox.error, computer.started, computer.stopped, computer.error
Files advanced (Phase 2)
sandbox.files
// Recursive directory tree
const tree = await sandbox.files.tree("/workspace", { recursive: true });
// Batch file write
await sandbox.files.writeMany([
{ path: "/workspace/app.ts", content: 'console.log("hello")' },
{ path: "/workspace/config.json", content: new TextEncoder().encode('{}') },
]);
// Watch for file-system changes (SSE async iterator)
for await (const event of sandbox.files.watch()) {
console.log(event.type, event.path); // "modified", "/workspace/app.ts"
} Sandbox environment variables (Phase 2)
sandbox.env
const vars = await sandbox.env.list();
await sandbox.env.set("DATABASE_URL", "postgres://...");
await sandbox.env.delete("DATABASE_URL"); Processes (Phase 2)
sandbox.processes
// Start a long-running background process
const proc = await sandbox.processes.start({
command: "npm run dev",
env: { NODE_ENV: "development" },
});
const list = await sandbox.processes.list();
const one = await sandbox.processes.get(proc.pid);
await sandbox.processes.stop(proc.pid);
const logs = await sandbox.processes.logs(proc.pid);
// Streaming process output (SSE)
for await (const line of sandbox.processes.stream(proc.pid)) {
process.stdout.write(line.data ?? "");
} Usage and quotas (Phase 3)
client.usage - client.quotas
// Usage rollup
const report = await client.usage.get({
externalUserId: "usr_123",
groupBy: "externalUserId", // or "externalProjectId", "workspaceId"
period: "30d", // or pass start/end ISO strings
});
// Set per-user quotas
await client.quotas.set("usr_123", {
maxSandboxes: 5,
maxConcurrent: 2,
maxStorageGb: 10,
maxCreditCents: 10000, // $100.00
});
const quota = await client.quotas.get("usr_123");
await client.quotas.delete("usr_123"); // revert to tenant default Audit log (Phase 3)
client.auditLog
let cursor: string | undefined;
do {
const page = await client.auditLog.list({ after: cursor, limit: 50 });
for (const entry of page.items) {
console.log(entry.action, entry.createdAt);
}
cursor = page.nextCursor ?? undefined;
} while (cursor); Tenant-wide event stream (Phase 3)
for await (const event of client.events.stream({ tenantWide: true })) {
console.log(event.type, event.data);
} Sharing (Phase 4)
sandbox.share
// Create a public share URL
const share = await sandbox.share.create({ expiresIn: 3600 });
// { shareId: "ms_...", shareUrl: "https://...", expiresAt: "...", scope: "read" }
const shares = await sandbox.share.list();
await sandbox.share.revoke(share.shareId); White-label integrator flow
import { Miosa } from "@miosa/sdk";
import { randomUUID } from "crypto";
const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! });
// 1. Configure tenant branding once at startup
await client.tenant.branding.set({
productName: "Acme AI",
logoUrl: "https://cdn.acme.com/logo.png",
primaryColor: "#ff6600",
});
// 2. Set a custom preview domain
await client.tenant.previewDomain.set("preview.acme.com");
await client.tenant.previewDomain.verify();
// 3. Register webhook for lifecycle events
await client.webhooks.create({
url: "https://api.acme.com/webhooks/miosa",
events: ["sandbox.created", "sandbox.ready", "sandbox.destroyed"],
});
// 4. Cap per-user resources
await client.quotas.set("usr_dr_smith", {
maxSandboxes: 3,
maxConcurrent: 1,
});
// 5. Create a sandbox attributed to a user + project
const sandbox = await client.sandboxes.create({
templateId: "miosa-sandbox",
name: "smile-dental",
externalUserId: "usr_dr_smith",
externalProjectId: "proj_landing_page",
externalWorkspaceId: "ws_smile_dental",
metadata: { plan: "pro" },
idempotencyKey: randomUUID(),
});
// 6. Write files and execute a build
await sandbox.files.writeMany([
{ path: "/workspace/index.html", content: "<h1>Hello</h1>" },
]);
const result = await sandbox.exec.run("echo build done");
// 7. Mint a preview token for the end user
const tokenInfo = await sandbox.previewToken({ expiresIn: 3600 });
const previewUrl = tokenInfo.url; // share with the end user
// 8. Create a public share link
const share = await sandbox.share.create({ expiresIn: 86400 });
const publicUrl = share.shareUrl;
// 9. Query usage
const report = await client.usage.get({
externalUserId: "usr_dr_smith",
period: "30d",
});
// 10. Clean up
await sandbox.destroy(); Common patterns
Idempotency key
import { randomUUID } from "crypto";
const sandbox = await client.sandboxes.create({
templateId: "miosa-sandbox",
idempotencyKey: randomUUID(),
}); Parallel sandbox creation
const [sb1, sb2] = await Promise.all([
client.sandboxes.create({ name: "worker-a" }),
client.sandboxes.create({ name: "worker-b" }),
]); Pagination
let cursor: string | undefined;
do {
const page = await client.auditLog.list({ after: cursor, limit: 50 });
for (const entry of page.items) {
console.log(entry.action, entry.createdAt);
}
cursor = page.nextCursor ?? undefined;
} while (cursor); Custom base URL (self-hosted)
const client = new Miosa({
apiKey: "msk_live_...",
baseUrl: "https://api.your-domain.com/api/v1",
}); Webhook verification
import { createHmac } from "crypto";
function verifyWebhook(payload: Buffer, signature: string, secret: string): boolean {
const expected = createHmac("sha256", secret).update(payload).digest("hex");
return expected === signature;
} Resource coverage
| Resource group | Available |
|---|---|
| Sandboxes (create, get, list, destroy, exec, events, files, previews, tags, snapshots) | ✓ |
| Sandbox Phase 1-4 (update, previewToken, fork, env, processes, share) | ✓ |
| Computers (CRUD + lifecycle) | ✓ |
| Computer sub-resources (terminal, env, ports, volumes, logs, auto-stop, OSA) | ✓ |
| Desktop (screenshot, click, type, drag, scroll, key, windows) | ✓ |
| Files (upload, download, list, delete) | ✓ |
| Exec | ✓ |
| Deployments | ✓ |
| Databases (managed Postgres) | ✓ |
| Storage (object storage) | ✓ |
| Volumes | ✓ |
| Custom domains (flat + per-deployment) | ✓ |
| Functions / cron jobs / health checks / webhooks | ✓ |
| Sandbox templates | ✓ |
| API keys | ✓ |
| Tenant (plan, preview domain, branding) | ✓ |
| Regions / settings | ✓ |
| Dashboard / analytics / audit log / usage / quotas | ✓ |
| Channels / integrations / project integrations | ✓ |
| External keys | ✓ |
| MCP | ✓ |
| Models / completions / embeddings | ✓ |
| Provider defaults / benchmarks | ✓ |
| Command center / builder sessions / community | ✓ |
| OpenComputers (hosts, jobs, files, tunnels, agents, clusters) | ✓ |
| Admin | ✓ |
| Workspace members + invites | ✓ |
| Org invites | ✓ |
| Snapshots (standalone) | ✓ |
| Credits | ✓ |