# MIOSA Documentation - Full Text Generated: 2026-05-18T15:03:14.334Z Canonical origin: https://miosa.ai Worker fallback origin: https://miosa.roberto-c49.workers.dev This file is generated from src/routes/docs. It is intended for AI agents, crawlers, search indexers, and customers who need a single plain-text copy of the public documentation. --- # MIOSA Documentation URL: https://miosa.ai/docs Fallback URL: https://miosa.roberto-c49.workers.dev/docs Source: src/routes/docs/+page.md Description: Build, preview, and deploy AI-generated apps on managed compute — one workspace API key, your customers attributed. # MIOSA Documentation
Quickstart → API Reference

Last updated:

MIOSA is the AI compute platform — a unified surface for building, deploying, and operating AI-powered apps. Spin up isolated sandboxes for code-gen agents, publish versioned deployments with rollbacks, attach managed Postgres and storage, and embed the whole thing into your own product without surfacing the MIOSA brand to your users. ## Get started with MIOSA Spin up your first sandbox in under two minutes. Connect the [Python](/docs/sdks/python/) or [TypeScript](/docs/sdks/typescript/) SDK, attribute resources to your downstream tenants with `external_workspace_id`, and publish to a stable production URL with one call. The [MIOSA SDK ecosystem](/docs/sdks/python/) provides idiomatic clients for five languages — Python, TypeScript, Go, Java, and Elixir — all wrapping the same REST API. Connect your [Git workflow](/docs/develop/sandboxes/) and your AI agent can write code directly into the sandbox, with previews proxied live to your browser before you publish. See the [Quickstart](/docs/quickstart/) for a full walkthrough, or the [Concepts](/docs/concepts/) page for the resource model. --- ## Quick references --- ## Build your applications Use one or more of the following primitives to build your application: - **[Sandboxes](/docs/develop/sandboxes/)**: Mutable Firecracker microVMs that boot warm in ~150 ms. Where your AI agent writes code, runs builds, and streams output. - **[Previews](/docs/develop/previews/)**: Live, share-tokened URLs proxied straight to a sandbox port. No build step, no preview-deploy round-trip. - **[Templates](/docs/develop/templates/)**: Pre-configured sandbox images (Next.js, React, Python, Go, more) or your own BuildSpec for reusable team templates. - **[Files and Exec](/docs/develop/files-and-exec/)**: Write, read, and list files; spawn processes; stream stdout/stderr — all over a single REST surface. - **[AI Agent integration](/docs/guides/agent/)**: Hook your agent loop to the sandbox API. The agent decides what to do; MIOSA executes. --- ## Use MIOSA's infrastructure Add AI-native compute to your platform: - **[Computers](/docs/computers/overview/)** New: Full Linux desktop VMs with screenshot, click, and type APIs for computer-use agents. - **[Snapshots](/docs/api-reference/snapshots/)**: Fork or rewind a sandbox state. Restore in under 200 ms. - **[Sandbox Templates BuildSpec](/docs/develop/templates/)** New: Author and publish reusable sandbox images for your team or the public catalog. - **[Events (SSE)](/docs/api-reference/events/)**: Subscribe to live build, publish, and runtime events with short-lived auth tickets. --- ## Embed MIOSA in your platform Run MIOSA underneath your own product without exposing the MIOSA brand to your end users: - **[Platform overview](/docs/platform/overview/)**: How MIOSA maps one workspace key to many downstream tenants. - **[External Attribution](/docs/platform/attribution/)**: Tag every resource with `external_workspace_id`, `external_user_id`, `external_project_id`. Usage and billing roll up automatically. - **[Browser Tokens](/docs/platform/browser-tokens/)**: Short-lived, scoped tokens that let browser code call MIOSA APIs without exposing your workspace key. - **[Usage and Billing](/docs/platform/usage-and-billing/)**: Per-tenant usage reports, spend caps, and billing delegation. --- ## Deploy and scale Ship code to production, manage versions, and operate at scale: - **[Deployments](/docs/deploy/overview/)**: The runtime object — points at one immutable Version at a time, exposes a public URL. - **[Publishing](/docs/deploy/publishing/)**: Turn a sandbox into a Version. Builder VMs, source snapshots, immutable releases, automatic promotion. - **[Versions](/docs/deploy/versions/)** and **[Releases](/docs/deploy/releases/)**: How immutable build artifacts and the metadata pointing at them are modeled. - **[Runtime Instances](/docs/deploy/runtime-instances/)**: Compute pods for dynamic deployments. Horizontal scale, health checks, autoscale-to-zero (planned). - **[Custom Domains](/docs/deploy/domains/)**: DNS + TLS for production. Per-deployment or per-Version subdomains; CNAME or apex. - **[Rollback](/docs/deploy/rollback/)**: Point a deployment at a previous Version in one call. Sub-second. --- ## Data services Managed data attached to your sandbox as env vars at boot: - **[Postgres](/docs/data/postgres/)**: Provision a database; `DATABASE_URL` injected. Branch and snapshot for staging environments (planned). - **[Storage / Buckets](/docs/data/storage/)**: S3-compatible object storage. Pre-signed URLs for browser uploads. - **[Redis / Cache](/docs/data/redis/)** Beta: Managed Redis for sessions, caches, and queues. - **[Auth as a Service](/docs/data/auth/)** Coming soon: User auth (signup, signin, sessions, OAuth) wired into your generated apps. - **[Volumes](/docs/data/volumes/)**: Persistent disk attached to a Runtime Instance. --- ## Explore guides and tutorials Extend your knowledge with deep-dives: - **[Concepts](/docs/concepts/)**: The full resource model — Sandbox, Preview, Deployment, Version, Runtime Instance, Domain. - **[Fundamentals](/docs/fundamentals/)**: How authentication, tenancy, and the request lifecycle work. - **[CLI](/docs/cli/)**: Scriptable access to every MIOSA surface from your terminal. - **[Changelog](/docs/changelog/)**: What shipped, what's next. - **[API Reference](/docs/api-reference/)**: Every public endpoint, request/response shape. --- [View full sitemap](/docs/api-reference/) --- # Interactive API Reference — MIOSA Docs URL: https://miosa.ai/docs/api Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api Source: src/routes/docs/api/+page.svelte Description: Browse and try every MIOSA API endpoint interactively. Covers sandboxes, computers, deployments, API keys, events, and more. {#if !mounted} {/if} --- # API Reference URL: https://miosa.ai/docs/api-reference Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference Source: src/routes/docs/api-reference/+page.md Description: REST API reference for the MIOSA platform — base URL, auth, request/response envelopes, and the full endpoint index. Want to browse and try endpoints without writing code? The Interactive API Reference renders the full OpenAPI spec with an in-browser request runner — no setup required. Every MIOSA API endpoint is REST over HTTPS, returns JSON, and follows a consistent envelope format. The SDKs wrap these endpoints — you can use them directly when you need low-level control or want to integrate from a language without an official SDK. ## Base URL ``` https://api.miosa.ai/api/v1 ``` ## Authentication All requests require a Bearer token: ```http Authorization: Bearer msk_live_... ``` MIOSA API keys (`msk_*`) and short-lived JWT tokens are both accepted. Keys are scoped to an organization and its workspaces/projects. See [API Keys](/docs/platform/api-keys/) for scope details. ## Request format - `Content-Type: application/json` for all request bodies. - File uploads use `multipart/form-data`. - All field names are **snake_case**. - Mutation requests (`POST`, `PUT`, `PATCH`, `DELETE`) should include an `Idempotency-Key` header to enable safe retries: ```http POST /api/v1/sandboxes Authorization: Bearer msk_live_... Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000 Content-Type: application/json { "template": "miosa-sandbox", "workspace_slug": "dr-smith-clinic", "project_slug": "lead-magnet", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789" } ``` Use a UUID v4 as the idempotency key. Repeating the same key within 24 hours returns the original response without re-executing the mutation. ## Response envelope Single-resource responses: ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "name": "my-sandbox", "status": "running", "created_at": "2026-05-17T10:00:00Z" } } ``` List responses include a pagination object: ```json { "data": [ { "id": "...", "name": "..." } ], "page": { "page": 1, "page_size": 20, "total": 150 } } ``` Pagination parameters accepted by all list endpoints: | Parameter | Type | Default | Max | |---|---|---|---| | `page` | integer | 1 | — | | `page_size` | integer | 20 | 100 | ## Error envelope All errors follow a single shape: ```json { "error": { "code": "sandbox_not_found", "message": "No sandbox with id sbx_abc exists in this workspace.", "request_id": "req_01jv..." } } ``` Include the `request_id` when contacting support. ### HTTP status codes | Status | Meaning | |---|---| | 200 | Success | | 201 | Created | | 202 | Accepted — async operation started | | 400 | Bad request — missing or invalid parameters | | 401 | Unauthorized — invalid or missing token | | 403 | Forbidden — valid token, insufficient scope | | 404 | Not found | | 409 | Conflict — invalid state transition (e.g. starting a running computer) | | 413 | Payload too large — file upload exceeds 10 MB | | 422 | Unprocessable entity — validation error | | 429 | Rate limited | | 500 | Internal server error | | 502 | Bad gateway — in-VM agent unreachable | ## Rate limits | Scope | Limit | |---|---| | General API | 300 req/min per workspace | | Auth endpoints | 20 req/min | | Public/unauthenticated | 60 req/min | ## IDs and timestamps All resource IDs are UUID v4. All timestamps are ISO 8601 in UTC: `2026-05-17T10:00:00Z`. ## Ownership fields Create endpoints for resources accept the same ownership selectors: | Field | Use | |---|---| | `workspace_id` | Existing MIOSA workspace UUID | | `workspace_slug` | Existing or auto-created workspace slug | | `workspace_name` | Display name used when auto-creating a workspace | | `project_id` | Existing MIOSA project UUID | | `project_slug` | Existing or auto-created project slug | | `project_name` | Display name used when auto-creating a project | | `external_workspace_id` | Your customer/account/workspace ID | | `external_user_id` | Your end-user ID | | `external_project_id` | Your project/app/document ID | Responses include `workspace_id` and `project_id` when the resource is owned by a workspace/project. See [Ownership and Attribution](/docs/platform/attribution/). --- ## Compute Create, list, get, stop, and delete sandbox environments. The core compute primitive. [Open →](/docs/api-reference/sandboxes/) Run bash commands and Python code inside a running computer or sandbox. [Open →](/docs/api-reference/exec/) Server-sent events stream for long-running commands — line-by-line stdout/stderr. [Open →](/docs/api-reference/streaming-exec/) Read, write, stat, mkdir, rename, copy, and chmod files inside a computer. [Open →](/docs/api-reference/files/) Directory listings, multi-file operations, and archive upload/download. [Open →](/docs/api-reference/filesystem/) Create, restore, and delete point-in-time snapshots of a computer's disk state. [Open →](/docs/api-reference/snapshots/) ## Deploy Manage static and server deployments — create, list, get, and delete. [Open →](/docs/api-reference/deployments/) Immutable deployment versions. Each publish creates a new version. [Open →](/docs/api-reference/versions/) Promote a version to a named release (production, staging, etc.). [Open →](/docs/api-reference/releases/) Register, verify, and manage custom domains on deployments. [Open →](/docs/api-reference/custom-domains/) ## Computers Full lifecycle for desktop VM computers — create, start, stop, restart, clone, resize. [Open →](/docs/api-reference/computers/) Desktop control actions: screenshot, click, type, key, scroll, drag, hotkey, windows, accessibility tree. [Open →](/docs/api-reference/desktop/) Register your own hardware as a MIOSA computer (BYOC). One command, any machine. [Open →](/docs/api-reference/open-computers/) ## Platform Customer/client workspaces inside an organization. [Open →](/docs/api-reference/workspaces/) Apps, websites, documents, and workflows inside a workspace. [Open →](/docs/api-reference/projects/) Managed background services attached to a computer (databases, queues, etc.). [Open →](/docs/api-reference/services/) Inbound and outbound network rules for a computer. [Open →](/docs/api-reference/network-policy/) Lifecycle events emitted by computers and sandboxes — for webhooks and audit logs. [Open →](/docs/api-reference/events/) Read credit balance, list usage transactions, and top up. [Open →](/docs/api-reference/credits/) List available regions and their current capacity. [Open →](/docs/api-reference/regions/) The SDKs expose every endpoint listed above. If you prefer not to make raw HTTP calls, start with the [Python SDK](https://pypi.org/project/miosa/) or [TypeScript SDK](https://www.npmjs.com/package/@miosa/sdk). --- # Computers API URL: https://miosa.ai/docs/api-reference/computers Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/computers Source: src/routes/docs/api-reference/computers/+page.md Description: API reference for creating, managing, and controlling MIOSA computers — full lifecycle, desktop control, files, metrics, and env vars. Computers are persistent desktop VMs — full Firecracker microVMs with a Linux desktop, VNC streaming, and a resident envd agent that accepts exec, file, and desktop commands. Base path: `/api/v1/computers`. Verbs supported: **GET** (list/get/config), **POST** (create/start/stop/restart/clone/resize), **PATCH** (update), **PUT** (update), **DELETE** (delete). All mutation endpoints (`POST`, `PATCH`, `PUT`, `DELETE`) accept an `Idempotency-Key` header. Use a UUID v4. Repeating the same key within 24 hours returns the original response without re-executing the mutation. Rate limit: 300 requests/min per workspace across all API endpoints. Exceeding the limit returns HTTP 429 with a `Retry-After` header. --- ## Create a Computer **`POST /api/v1/computers`** Creates a new computer and begins provisioning. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Display name | | `template_type` | string | Yes | Template (`"miosa-desktop"`) | | `size` | string | No | `"small"` (4GB/1CPU), `"medium"` (8GB/2CPU), `"large"` (16GB/4CPU). Default: `"small"` | | `selected_apps` | string[] | No | Applications to install after boot | | `workspace_id` | UUID | No | Workspace to assign this computer to. Defaults to the tenant's default workspace. | | `workspace_slug` | string | No | Existing or auto-created workspace slug. | | `workspace_name` | string | No | Workspace display name if auto-created. | | `project_id` | UUID | No | Project to assign this computer to. Defaults to the workspace default project. | | `project_slug` | string | No | Existing or auto-created project slug. | | `project_name` | string | No | Project display name if auto-created. | | `external_workspace_id` | string | No | Your own customer/account/workspace ID. | | `external_user_id` | string | No | Your own end-user ID. | | `external_project_id` | string | No | Your own project/app/document ID. | ### Response — `201 Created` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "tenant_id": "...", "owner_user_id": "...", "name": "my-computer", "slug": "my-computer", "template_type": "miosa-desktop", "size": "small", "vcpus": 1, "memory_mb": 4096, "status": "provisioning", "vm_id": null, "workspace_id": "660e8400-e29b-41d4-a716-446655440001", "project_id": "770e8400-e29b-41d4-a716-446655440002", "external_workspace_id": null, "external_user_id": null, "external_project_id": null, "sandbox_url": "https://my-computer.sandbox.miosa.ai", "desktop_url": "https://my-computer.sandbox.miosa.ai/desktop/index.html", "selected_apps": [], "settings": {}, "ai_config": {}, "agent_session_id": null, "agent_status": null, "resolution": "1280x720", "auto_stop": 0, "created_at": "2026-04-11T00:00:00Z", "updated_at": "2026-04-11T00:00:00Z" } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 400 | `name is required` | Missing name or template_type | | 402 | `INSUFFICIENT_CREDITS` | Not enough credits to create | | 422 | `TENANT_RESOLUTION_FAILED` | Cannot determine tenant for user | ```bash curl -X POST https://api.miosa.ai/api/v1/computers \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "my-computer", "template_type": "miosa-desktop", "size": "small", "workspace_slug": "dr-smith-clinic", "workspace_name": "Dr. Smith Clinic", "project_slug": "records-portal", "project_name": "Records Portal", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789" }' ``` --- ## List Computers **`GET /api/v1/computers`** Returns all computers belonging to the authenticated user's tenant. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `workspace_id` | UUID | Filter computers to a specific workspace. Omit to return computers across all workspaces. | | `project_id` | UUID | Filter computers to a specific project. | | `external_workspace_id` | string | Filter by your customer/account/workspace ID. | | `external_user_id` | string | Filter by your end-user ID. | | `external_project_id` | string | Filter by your project/app/document ID. | ### Response — `200 OK` ```json { "computers": [ { "id": "...", "name": "my-computer", "status": "running", "size": "small", "created_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash # All computers curl https://api.miosa.ai/api/v1/computers \ -H "Authorization: Bearer $MIOSA_API_KEY" # Filtered to a workspace curl "https://api.miosa.ai/api/v1/computers?workspace_id=660e8400-e29b-41d4-a716-446655440001" \ -H "Authorization: Bearer $MIOSA_API_KEY" # Filtered to a project curl "https://api.miosa.ai/api/v1/computers?project_id=770e8400-e29b-41d4-a716-446655440002" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get a Computer **`GET /api/v1/computers/{id}`** ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Computer ID | ### Response — `200 OK` Full computer object (same as create response, with current status). ### Errors | Status | Error | Cause | |--------|-------|-------| | 400 | `invalid computer id` | Not a valid UUID | | 404 | `computer not found` | Does not exist or belongs to different tenant | ```bash curl https://api.miosa.ai/api/v1/computers/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Update a Computer **`PATCH /api/v1/computers/{id}`** Currently supports updating `agent_session_id` only. ### Request Body | Field | Type | Description | |-------|------|-------------| | `agent_session_id` | string | Link an AI agent session | ### Response — `200 OK` Updated computer object. ```bash curl -X PATCH https://api.miosa.ai/api/v1/computers/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"agent_session_id": "session-uuid"}' ``` --- ## Delete a Computer **`DELETE /api/v1/computers/{id}`** Permanently destroys the VM and removes the computer record. ### Response — `200 OK` ```json { "id": "550e8400-e29b-41d4-a716-446655440000", "deleted": true } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `not a member of this computer` | No access to this computer | | 404 | `computer not found` | Does not exist | ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Start a Computer **`POST /api/v1/computers/{id}/start`** Resumes a stopped or paused computer. ### Response — `200 OK` Updated computer object with `status: "running"`. ### Errors | Status | Error | Cause | |--------|-------|-------| | 402 | `INSUFFICIENT_CREDITS` | Not enough credits | | 409 | `computer cannot be started from its current status` | Not in a startable state | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/start \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Stop a Computer **`POST /api/v1/computers/{id}/stop`** Pauses a running computer. Can be restarted later. ### Response — `200 OK` Updated computer object with `status: "stopped"`. ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `computer cannot be stopped from its current status` | Not running | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/stop \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Restart a Computer **`POST /api/v1/computers/{id}/restart`** Stops and immediately restarts a running computer. ### Response — `200 OK` Updated computer object. ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `computer must be running to restart` | Not running | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/restart \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Auto-Stop Configuration **`GET /api/v1/computers/{id}/auto-stop`** ### Response — `200 OK` ```json { "auto_stop_seconds": 3600, "enabled": true } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/auto-stop \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Update Auto-Stop Configuration **`PATCH /api/v1/computers/{id}/auto-stop`** ### Request Body | Field | Type | Description | |-------|------|-------------| | `auto_stop_seconds` | integer | Seconds of inactivity before auto-stop. `0` or `null` to disable. | ### Response — `200 OK` Updated auto-stop configuration. ```bash curl -X PATCH https://api.miosa.ai/api/v1/computers/{id}/auto-stop \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"auto_stop_seconds": 3600}' ``` --- ## Get VNC Credentials **`GET /api/v1/computers/{id}/vnc-credentials`** Returns credentials for direct VNC desktop access. ### Response — `200 OK` ```json { "vnc_url": "https://my-computer.sandbox.miosa.ai/desktop/index.html", "ws_url": "wss://my-computer.sandbox.miosa.ai/ws/vnc/550e8400-e29b-41d4-a716-446655440000?auth=stream_token", "token": "stream_auth_token", "expires_at": 1712700060, "computer_id": "550e8400-e29b-41d4-a716-446655440000", "slug": "my-computer" } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/vnc-credentials \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Stream Token **`POST /api/v1/computers/{id}/stream-token`** Returns a time-limited auth token for VNC and terminal WebSocket connections. ### Response — `200 OK` ```json { "token": "stream_auth_token", "expires_at": 1712700060 } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/stream-token \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Computer URLs **`GET /api/v1/computers/{id}/urls`** Returns all connection URLs for the computer. ### Response — `200 OK` ```json { "desktop_url": "https://my-computer.sandbox.miosa.ai/desktop/index.html", "vnc_url": "https://my-computer.sandbox.miosa.ai/desktop/index.html", "terminal_url": "wss://my-computer.sandbox.miosa.ai/ws/terminal/550e8400-e29b-41d4-a716-446655440000", "computer_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/urls \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## List Installed Apps **`GET /api/v1/computers/{id}/apps`** Returns the list of applications installed on the computer. This endpoint is proxied to the in-VM envd daemon. ### Response — `200 OK` Proxied response from envd containing the list of installed applications. ```bash curl https://api.miosa.ai/api/v1/computers/{id}/apps \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Clone a Computer **`POST /api/v1/computers/{id}/clone`** Creates a new computer from the current state of an existing one. The source computer must be running; the clone starts in `provisioning` and progresses to `running`. ### Response — `201 Created` Full computer object for the new clone with its own ID. ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/clone \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Resize a Computer **`POST /api/v1/computers/{id}/resize`** Changes the vCPU and memory allocation. The computer must be stopped first. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `size` | string | Yes | `"small"` (4 GB/1 CPU), `"medium"` (8 GB/2 CPU), `"large"` (16 GB/4 CPU) | ### Response — `200 OK` Updated computer object with new `size`, `vcpus`, and `memory_mb`. ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/resize \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"size": "medium"}' ``` --- ## Move a Computer **`POST /api/v1/computers/{id}/move`** Migrates a computer to a different region. The computer must be stopped. Live migration is not supported; the computer will be moved offline. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `region` | string | Yes | Target region slug (e.g. `"us-east-ny"`) | ### Response — `200 OK` Updated computer object with new `region`. ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/move \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"region": "us-east-ny"}' ``` --- ## Get Metrics **`GET /api/v1/computers/{id}/metrics`** Returns time-series CPU, RAM, and credit-burn metrics. Useful for dashboards and auto-stop logic. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `window` | string | Time window: `"1h"` (default), `"24h"`, `"7d"` | ### Response — `200 OK` ```json { "cpu_percent": [ {"ts": "2026-05-17T10:00:00Z", "value": 12.4}, {"ts": "2026-05-17T10:01:00Z", "value": 8.1} ], "memory_percent": [ {"ts": "2026-05-17T10:00:00Z", "value": 45.2} ], "credits_per_hour": 3 } ``` ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/metrics?window=1h" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Env Vars Encrypted per-computer environment variables. Values are decrypted at boot and injected into the VM environment. ### List **`GET /api/v1/computers/{id}/env`** ```bash curl https://api.miosa.ai/api/v1/computers/{id}/env \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ### Create **`POST /api/v1/computers/{id}/env`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Variable name (e.g. `DATABASE_URL`) | | `value` | string | Yes | Plaintext value — encrypted at rest | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/env \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "DATABASE_URL", "value": "postgres://user:pass@host/db"}' ``` ### Update **`PATCH /api/v1/computers/{id}/env/{name}`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `value` | string | Yes | New plaintext value | ### Delete **`DELETE /api/v1/computers/{id}/env/{name}`** Returns `200 OK`. --- ## Port Exposure Control per-port visibility for services running inside the computer. ### List Ports **`GET /api/v1/computers/{id}/ports`** ### Expose a Port **`POST /api/v1/computers/{id}/ports`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `port` | integer | Yes | Port number (1–65535) | | `visibility` | string | Yes | `"public"`, `"private"`, or `"protected"` | ### Update Port Visibility **`PATCH /api/v1/computers/{id}/ports/{port}`** ### Remove Port **`DELETE /api/v1/computers/{id}/ports/{port}`** ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/ports \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"port": 3000, "visibility": "public"}' ``` --- ## Common Errors | Status | Code | Cause | |--------|------|-------| | 400 | `invalid computer id` | Path parameter is not a valid UUID | | 402 | `INSUFFICIENT_CREDITS` | Not enough credits to start or create | | 403 | `not a member of this computer` | No access to this computer | | 404 | `computer not found` | Does not exist or belongs to a different tenant | | 409 | `COMPUTER_NOT_RUNNING` | Operation requires a running computer | | 502 | `AGENT_UNAVAILABLE` | In-VM envd agent unreachable | --- ## See also - [Desktop API](/docs/api-reference/desktop/) — screenshot, click, type, scroll, drag, windows - [Files API](/docs/api-reference/files/) — read, write, stat, mkdir, rename, copy, chmod, download - [Exec API](/docs/api-reference/exec/) — bash and Python execution - [Snapshots API](/docs/api-reference/snapshots/) — create, restore, and delete checkpoints - [Events (SSE)](/docs/api-reference/events/) — computer lifecycle event stream - [Regions](/docs/api-reference/regions/) — available regions for computer placement --- # Credits API URL: https://miosa.ai/docs/api-reference/credits Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/credits Source: src/routes/docs/api-reference/credits/+page.md Description: API reference for checking credit balance, transaction history, and usage data. MIOSA uses a credit-based billing system. Credits are consumed for compute time and AI API calls. Base path: `/api/v1/credits` --- ## Get Balance **`GET /api/v1/credits/balance`** Returns the current credit balance for your tenant. ### Response — `200 OK` ```json { "tenant_id": "550e8400-e29b-41d4-a716-446655440000", "balance_credits": 850, "lifetime_earned": 1000, "lifetime_spent": 150, "credit_expiry_at": "2026-10-08T00:00:00Z", "updated_at": "2026-04-11T15:00:00Z" } ``` ### Response Fields | Field | Type | Description | |-------|------|-------------| | `tenant_id` | UUID | Your tenant ID | | `balance_credits` | integer | Current available credits | | `lifetime_earned` | integer | Total credits ever earned (purchases + promos) | | `lifetime_spent` | integer | Total credits ever spent | | `credit_expiry_at` | ISO 8601 | When current credits expire (180 days from purchase) | | `updated_at` | ISO 8601 | Last balance change | If no credit balance record exists yet, returns zeros: ```json { "tenant_id": "...", "balance_credits": 0, "lifetime_earned": 0, "lifetime_spent": 0, "credit_expiry_at": null, "updated_at": null } ``` ```bash curl https://api.miosa.ai/api/v1/credits/balance \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Transactions **`GET /api/v1/credits/transactions`** Returns paginated credit transaction history. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `page` | integer | 1 | Page number (1-based) | | `page_size` | integer | 20 | Items per page (max: 100) | ### Response — `200 OK` ```json { "transactions": [ { "id": "uuid", "tenant_id": "uuid", "user_id": "uuid", "computer_id": "uuid", "type": "spend", "amount_credits": -5, "description": "LLM API call", "provider": "ollama", "model": "nemotron-3-super", "input_tokens": 1500, "output_tokens": 500, "cost_usd_micros": 250, "inserted_at": "2026-04-11T15:00:00Z" }, { "id": "uuid", "tenant_id": "uuid", "user_id": "uuid", "computer_id": null, "type": "earn", "amount_credits": 1000, "description": "Starter plan monthly credits", "provider": null, "model": null, "input_tokens": null, "output_tokens": null, "cost_usd_micros": null, "inserted_at": "2026-04-01T00:00:00Z" } ], "total": 47, "page": 1, "page_size": 20 } ``` ### Transaction Fields | Field | Type | Description | |-------|------|-------------| | `id` | UUID | Transaction ID | | `tenant_id` | UUID | Tenant ID | | `user_id` | UUID | User who triggered the transaction | | `computer_id` | UUID | Associated computer (if applicable) | | `type` | string | `"earn"` or `"spend"` | | `amount_credits` | integer | Credits (positive = earned, negative = spent) | | `description` | string | Human-readable description | | `provider` | string | LLM provider (for AI transactions) | | `model` | string | Model name (for AI transactions) | | `input_tokens` | integer | Input tokens used (for AI transactions) | | `output_tokens` | integer | Output tokens generated (for AI transactions) | | `cost_usd_micros` | integer | Cost in microdollars (1/1,000,000 USD) | | `inserted_at` | ISO 8601 | Transaction timestamp | ```bash curl "https://api.miosa.ai/api/v1/credits/transactions?page=1&page_size=50" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Usage Summary **`GET /api/v1/credits/usage`** Returns daily usage rollups grouped by provider and model. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `from` | ISO 8601 | 30 days ago | Start of date range | | `to` | ISO 8601 | Now | End of date range | ### Response — `200 OK` ```json { "usage": [ { "day": "2026-04-11T00:00:00Z", "provider": "ollama", "model": "nemotron-3-super", "credits_spent": 45, "input_tokens": 15000, "output_tokens": 5000, "cost_usd_micros": 2250, "call_count": 12 }, { "day": "2026-04-10T00:00:00Z", "provider": "ollama", "model": "nemotron-3-super", "credits_spent": 30, "input_tokens": 10000, "output_tokens": 3000, "cost_usd_micros": 1500, "call_count": 8 } ], "from": "2026-03-12T00:00:00Z", "to": "2026-04-11T15:00:00Z" } ``` ### Usage Entry Fields | Field | Type | Description | |-------|------|-------------| | `day` | ISO 8601 | Date (truncated to day) | | `provider` | string | LLM provider | | `model` | string | Model name | | `credits_spent` | integer | Total credits spent that day | | `input_tokens` | integer | Total input tokens | | `output_tokens` | integer | Total output tokens | | `cost_usd_micros` | integer | Total cost in microdollars | | `call_count` | integer | Number of API calls | ### Errors | Status | Error | Cause | |--------|-------|-------| | 400 | `invalid 'from' timestamp` | Not valid RFC 3339 | | 400 | `invalid 'to' timestamp` | Not valid RFC 3339 | | 400 | `'from' must be before 'to'` | Invalid date range | ```bash curl "https://api.miosa.ai/api/v1/credits/usage?from=2026-04-01T00:00:00Z&to=2026-04-11T23:59:59Z" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Credit Pricing ### Compute Credits | Size | Credits/hour | |------|-------------| | Small (4GB/1CPU) | 3 | | Medium (8GB/2CPU) | 6 | | Large (16GB/4CPU) | 12 | ### AI Credits (per 1M tokens) | Tier | Input | Output | |------|-------|--------| | Standard models | 10 | 20 | | Advanced models | 60 | 100 | ### Plans | Plan | Price/month | Credits | |------|------------|---------| | Free | $0 | 100 | | Starter | $29 | 1,000 | | Pro | $79 | 3,000 | | Scale | $199 | 10,000 | Credits expire **180 days** from purchase. Unused plan credits do not roll over. --- ## See also - [API Reference overview](/docs/api-reference/) — base URL and authentication - [Error Codes](/docs/api-reference/errors/) — `INSUFFICIENT_CREDITS` error handling - [Workspaces](/docs/api-reference/workspaces/) — workspace and tenant management --- # Custom Domains API URL: https://miosa.ai/docs/api-reference/custom-domains Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/custom-domains Source: src/routes/docs/api-reference/custom-domains/+page.md Description: API reference for mapping tenant-owned FQDNs to MIOSA previews with automatic TLS. Custom domains let you expose MIOSA previews, computers, and deployments under tenant-owned FQDNs. There are four related surfaces: - **Computer custom domains** map one fully qualified domain, such as `app.yourdomain.com`, to one Computer service. - **Deployment custom domains** map one fully qualified domain, such as `program.drsmithclinic.com`, to one Deployment. - **Tenant preview domains** white-label generated preview links for the whole organization. After configuration, sandbox `/expose` responses use URLs such as `https://5173-sbx01j9x.sandbox.cliniciq.dev`. - **Workspace and project preview domains** override the organization preview domain for one client workspace or one project. MIOSA handles verification and automatic TLS certificate issuance via Caddy on-demand TLS. Nested base paths: `/api/v1/computers/{id}/domains` and `/api/v1/deployments/{id}/domains`. Flat frontend-compatible base path: `/api/v1/custom-domains`. Custom domains must not end in `miosa.ai` — that namespace is reserved for platform subdomains. Use a domain you control. --- ## Quick Start ```typescript const client = new Miosa(); // 1. Register the domain const domain = await client.domains.register(computerId, { domain: 'app.yourcompany.com', }); console.log(domain.verificationTarget); // e.g. "verify.miosa.ai" // 2. Add a CNAME record at your DNS provider: // app.yourcompany.com → verify.miosa.ai // 3. Trigger verification const verified = await client.domains.verify(computerId, domain.id); console.log(verified.status); // "verified" or "failed" ``` ```bash # Register curl -X POST https://api.miosa.ai/api/v1/computers/{id}/domains \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "app.yourcompany.com"}' ``` --- ## Endpoints ### Computer custom domains | Method | Path | Description | |--------|------|-------------| | `POST` | `/computers/{id}/domains` | Register a custom domain | | `GET` | `/computers/{id}/domains` | List domains for a computer | | `POST` | `/computers/{id}/domains/{domain_id}/verify` | Trigger DNS verification | | `DELETE` | `/computers/{id}/domains/{domain_id}` | Remove a domain | ### Deployment custom domains | Method | Path | Description | |--------|------|-------------| | `POST` | `/deployments/{id}/domains` | Register a custom domain | | `GET` | `/deployments/{id}/domains` | List domains for a deployment | ### Flat custom domains | Method | Path | Description | |--------|------|-------------| | `GET` | `/custom-domains?computer_id={id}` | List domains for a computer | | `GET` | `/custom-domains?deployment_id={id}` | List domains for a deployment | | `POST` | `/custom-domains` | Register a domain with `computer_id` or `deployment_id` | | `DELETE` | `/custom-domains/{id}` | Remove a domain | ### Tenant preview domains | Method | Path | Description | |--------|------|-------------| | `GET` | `/tenant/preview-domain` | Read the tenant preview domain | | `PUT` | `/tenant/preview-domain` | Set the tenant preview domain | | `DELETE` | `/tenant/preview-domain` | Clear the tenant preview domain | | `GET` | `/tenant/preview-domain/verify` | Check DNS/TLS readiness | ### Workspace and project preview domains | Method | Path | Description | |--------|------|-------------| | `GET` | `/workspaces/{id}/preview-domain` | Read the workspace preview domain | | `PUT` | `/workspaces/{id}/preview-domain` | Set the workspace preview domain | | `DELETE` | `/workspaces/{id}/preview-domain` | Clear the workspace preview domain | | `GET` | `/workspaces/{id}/preview-domain/verify` | Check workspace DNS readiness | | `GET` | `/projects/{id}/preview-domain` | Read the project preview domain | | `PUT` | `/projects/{id}/preview-domain` | Set the project preview domain | | `DELETE` | `/projects/{id}/preview-domain` | Clear the project preview domain | | `GET` | `/projects/{id}/preview-domain/verify` | Check project DNS readiness | --- ## White-label Managed Links Use a tenant preview domain when generated app/artifact preview URLs should use your organization's domain instead of the platform default `miosa.app`. ```bash curl -X PUT https://api.miosa.ai/api/v1/tenant/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"preview_domain":"preview.yourcompany.com"}' ``` Then expose a sandbox port: ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/expose \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"port":5173}' ``` Response: ```json { "url": "https://5173-sbx01j9x.sandbox.preview.yourcompany.com" } ``` For a client workspace domain: ```bash curl -X PUT https://api.miosa.ai/api/v1/workspaces/$WORKSPACE_ID/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"preview_domain":"drsmithclinic.com"}' ``` For a single project domain: ```bash curl -X PUT https://api.miosa.ai/api/v1/projects/$PROJECT_ID/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"preview_domain":"program.drsmithclinic.com"}' ``` DNS must delegate wildcard traffic to the MIOSA preview router: | Record type | Name | Value | Used for | |---|---|---|---| | `CNAME` | `*` | `proxy.miosa.ai` | `https://{slug}.{domain}` managed deployment/computer/default preview URLs | | `CNAME` | `*.sandbox` | `proxy.miosa.ai` | `https://{port}-{slug}.sandbox.{domain}` sandbox port previews | If no preview domain is configured, MIOSA returns the managed `miosa.app` fallback. This fallback remains available even after custom domains are attached. Preview-domain precedence is project → workspace → tenant → MIOSA fallback. Exact deployment/computer custom domains still take priority over preview-domain inheritance. --- ## Register a Domain **`POST /api/v1/computers/{id}/domains`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `domain` | string | Yes | Fully qualified domain name. RFC 1123 hostname, max 253 chars | ### Response — `201 Created` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "computer_id": "...", "deployment_id": null, "tenant_id": "...", "workspace_id": "660e8400-e29b-41d4-a716-446655440001", "project_id": "770e8400-e29b-41d4-a716-446655440002", "fqdn": "app.yourcompany.com", "status": "pending", "verification_target": "verify.miosa.ai", "verified_at": null, "tls_issued_at": null, "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789", "created_at": "2026-04-11T00:00:00Z", "updated_at": "2026-04-11T00:00:00Z" } } ``` ### Status Values | Status | Description | |--------|-------------| | `pending` | Registered; awaiting DNS verification | | `verified` | CNAME confirmed; Caddy may issue a cert | | `active` | TLS certificate issued and in use | | `failed` | Verification or TLS issuance failed | | `removed` | Domain detached (soft marker before deletion) | ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `has already been taken` | FQDN already registered (globally unique) | | 422 | Validation error | Invalid FQDN format or ends with `miosa.ai` | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/domains \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "preview.yourapp.io"}' ``` ### Register a deployment domain ```bash curl -X POST https://api.miosa.ai/api/v1/deployments/{id}/domains \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"domain": "program.drsmithclinic.com"}' ``` Deployment domains inherit `workspace_id`, `project_id`, and external attribution from the deployment. --- ## List Domains **`GET /api/v1/computers/{id}/domains`** ### Response — `200 OK` ```json { "data": [ { "id": "...", "fqdn": "app.yourcompany.com", "status": "active", "verified_at": "2026-04-11T01:00:00Z", "tls_issued_at": "2026-04-11T01:05:00Z", "created_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/domains \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Verify a Domain **`POST /api/v1/computers/{id}/domains/{domain_id}/verify`** Checks that the CNAME record at `fqdn` resolves to `verification_target`. On success the domain transitions to `"verified"` and Caddy will issue a TLS certificate on the next HTTPS request to the FQDN. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Computer ID | | `domain_id` | UUID | Domain ID | ### Response — `200 OK` ```json { "data": { "id": "...", "fqdn": "app.yourcompany.com", "status": "verified", "verified_at": "2026-04-11T01:00:00Z" } } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `domain is already verified` | Already completed | | 422 | `CNAME not found` | DNS record missing or not yet propagated | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/domains/{domain_id}/verify \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Remove a Domain **`DELETE /api/v1/computers/{id}/domains/{domain_id}`** Detaches the domain from the computer. The certificate is not revoked immediately (Caddy lets it expire naturally), but the domain will no longer route to the computer. ### Response — `200 OK` ```json { "data": { "id": "...", "fqdn": "app.yourcompany.com", "status": "removed" } } ``` ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/domains/{domain_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## DNS Setup Reference After registering a domain, add the following DNS record at your registrar or DNS provider: | Record | Host | Value | |--------|------|-------| | CNAME | `app` (or full subdomain) | `verify.miosa.ai` | DNS propagation typically takes 1–60 minutes. Call `verify` once propagation is complete. If verification fails, wait a few minutes and retry — the endpoint is idempotent. --- ## Common Recipes ### Automate full domain onboarding ```python from miosa import Miosa client = Miosa() domain = client.domains.register(computer_id, fqdn="api.yourcompany.com") print(f"Add CNAME: {domain.fqdn} → {domain.verification_target}") print("Waiting for DNS propagation...") while True: result = client.domains.verify(computer_id, domain.id) if result.status == "verified": print("Domain verified! TLS will be issued on first HTTPS request.") break elif result.status == "failed": raise RuntimeError("Verification failed — check your DNS records.") time.sleep(30) ``` ### List all pending domains (potential stuck ACME issuances) ```typescript const computers = await client.computers.list(); for (const computer of computers.data) { const { data: domains } = await client.domains.list(computer.id); const pending = domains.filter(d => d.status === 'pending'); if (pending.length > 0) { console.log(`${computer.name}: ${pending.length} domain(s) pending verification`); } } ``` --- # API Reference / Deployments URL: https://miosa.ai/docs/api-reference/deployments Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/deployments Source: src/routes/docs/api-reference/deployments/+page.md Description: REST API for the Deployment resource — create, list, get, update, delete, publish, rollback. A **Deployment** is the stable production object for a published app/site/API. See [Deploy / Overview](/docs/deploy/overview/) for the conceptual model. Sandbox-sourced create + publish + rollback are part of Phase 2B / 3 of the deployment refactor. Today, the backward-compatible `POST /api/v1/sandboxes/:id/deploy` route exists and uses the sandbox-backed bridge. Endpoints below show the steady-state shape; in-progress endpoints are marked. ## Endpoints ```http POST /api/v1/projects/:project_id/deployments # Phase 2B GET /api/v1/deployments GET /api/v1/deployments/:id PATCH /api/v1/deployments/:id DELETE /api/v1/deployments/:id POST /api/v1/deployments/:id/publish # Phase 2B POST /api/v1/deployments/:id/rollback # Phase 2B POST /api/v1/sandboxes/:id/deploy # Backward-compat ``` All endpoints require `Authorization: Bearer <msk_*>`. Mutations should include an `Idempotency-Key` header. ## Create ```http POST /api/v1/projects/:project_id/deployments ``` Body: ```json { "name": "Smile Dental Landing", "source_type": "sandbox", "workspace_slug": "dr-smith-clinic", "workspace_name": "Dr. Smith Clinic", "project_slug": "smile-dental-landing", "project_name": "Smile Dental Landing", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789", "metadata": { } } ``` Ownership fields accepted on create: `workspace_id`, `workspace_slug`, `workspace_name`, `project_id`, `project_slug`, `project_name`, `external_workspace_id`, `external_user_id`, and `external_project_id`. Scopes: `deployments:write`. ## Publish ```http POST /api/v1/deployments/:id/publish ``` Body: ```json { "source_sandbox_id": "sbx_...", "kind": "auto", "environment": "production", "output_path": "/workspace", "build_command": "npm run build", "run_command": "npm start", "port": 3000, "health_check_path": "/_health", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "external_workspace_id": "clinic_123" } ``` Server-side behavior is documented in [Publishing](/docs/deploy/publishing/). Response: ```json { "data": { "deployment": { "id": "dep_...", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "active_version_id": "ver_...", "state": "running", "public_url": "https://smile-dental.cliniciq.miosa.app" }, "version": { "id": "ver_...", "kind": "static", "state": "ready", "artifact_uri": "s3://...", "artifact_sha256": "...", "source_sha256": "..." }, "services": [ { "id": "svc_...", "type": "static_web", "state": "healthy" } ], "promoted": true } } ``` ## List ```http GET /api/v1/deployments GET /api/v1/deployments?workspace_id=550e8400-e29b-41d4-a716-446655440000 GET /api/v1/deployments?project_id=660e8400-e29b-41d4-a716-446655440001 GET /api/v1/deployments?external_workspace_id=clinic_123 GET /api/v1/deployments?external_user_id=dr-smith-456 GET /api/v1/deployments?state=running ``` All filters are organization-scoped server-side. See [Ownership and Attribution](/docs/platform/attribution/). ## Get ```http GET /api/v1/deployments/:id ``` Returns the full deployment row including `workspace_id`, `project_id`, `active_version_id`, attribution, and metadata. ## Update ```http PATCH /api/v1/deployments/:id ``` Body: any subset of mutable fields (`name`, `auto_deploy`, `metadata`, `external_*`). ## Rollback ```http POST /api/v1/deployments/:id/rollback ``` Body: ```json { "version_id": "ver_..." } ``` See [Rollback](/docs/deploy/rollback/). ## Delete ```http DELETE /api/v1/deployments/:id ``` Tears down runtime instances, removes routes, marks the deployment deleted. Versions and releases are retained per the retention policy. ## See also - [Versions](/docs/api-reference/versions/) — version sub-resource - [Releases](/docs/api-reference/releases/) — build artifact reference (Phase 2B) - [Custom Domains](/docs/api-reference/custom-domains/) — domain sub-resource - [Deploy / Overview](/docs/deploy/overview/) — conceptual model --- # Desktop API URL: https://miosa.ai/docs/api-reference/desktop Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/desktop Source: src/routes/docs/api-reference/desktop/+page.md Description: API reference for all desktop control endpoints — screenshot, mouse, keyboard, clipboard, windows, accessibility tree. Desktop control endpoints proxy commands to the resident envd agent inside the computer VM via the MIOSA control plane. All coordinates are in screen pixels (0,0 = top-left). Base path: `/api/v1/computers/{id}/desktop` All desktop endpoints require the computer to be in `"running"` status. Returns `409 COMPUTER_NOT_RUNNING` if the computer is stopped or provisioning. Rate limit: 300 req/min per workspace. Individual desktop actions are fast but high-frequency AI agents should batch operations where possible. --- ## Screenshot **`GET /api/v1/computers/{id}/desktop/screenshot`** Captures the full desktop as a PNG image. ### Response — `200 OK` Binary PNG data with `Content-Type: image/png`. ```bash curl https://api.miosa.ai/api/v1/computers/{id}/desktop/screenshot \ -H "Authorization: Bearer $MIOSA_API_KEY" \ --output screenshot.png ``` --- ## Screenshot Region **`POST /api/v1/computers/{id}/desktop/screenshot/region`** Captures a rectangular region of the screen. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | Left edge X coordinate | | `y` | integer | Yes | Top edge Y coordinate | | `width` | integer | Yes | Width in pixels | | `height` | integer | Yes | Height in pixels | ### Response — `200 OK` Binary PNG data. ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/screenshot/region \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"x": 0, "y": 0, "width": 800, "height": 600}' \ --output region.png ``` --- ## Click **`POST /api/v1/computers/{id}/desktop/click`** Performs a mouse click at the specified coordinates. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | X coordinate | | `y` | integer | Yes | Y coordinate | | `button` | string | No | `"left"` (default), `"right"`, `"middle"` | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/click \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"x": 500, "y": 300}' ``` --- ## Double-Click **`POST /api/v1/computers/{id}/desktop/double-click`** Performs a double-click at the specified coordinates. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | X coordinate | | `y` | integer | Yes | Y coordinate | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/double-click \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"x": 500, "y": 300}' ``` --- ## Type Text **`POST /api/v1/computers/{id}/desktop/type`** Types a string of text as keyboard input. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `text` | string | Yes | Text to type | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/type \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Hello, World!"}' ``` --- ## Key Press **`POST /api/v1/computers/{id}/desktop/key`** Presses a key or key combination. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | string | Yes | Key name or combination | Key combinations use `+` as separator: `"ctrl+c"`, `"alt+Tab"`, `"ctrl+shift+t"`. ### Common Keys | Key | Value | |-----|-------| | Enter | `Return` | | Tab | `Tab` | | Escape | `Escape` | | Backspace | `BackSpace` | | Delete | `Delete` | | Space | `space` | | Arrows | `Up`, `Down`, `Left`, `Right` | | Function | `F1`...`F12` | | Home/End | `Home`, `End` | | Page Up/Down | `Page_Up`, `Page_Down` | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/key \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"key": "ctrl+c"}' ``` --- ## Scroll **`POST /api/v1/computers/{id}/desktop/scroll`** Scrolls the mouse wheel at the specified position. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | X coordinate | | `y` | integer | Yes | Y coordinate | | `direction` | string | Yes | `"up"` or `"down"` | | `amount` | integer | No | Number of scroll steps (default: 3) | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/scroll \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"x": 500, "y": 300, "direction": "down", "amount": 5}' ``` --- ## Drag **`POST /api/v1/computers/{id}/desktop/drag`** Performs a click-and-drag from one point to another. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `start_x` | integer | Yes | Starting X coordinate | | `start_y` | integer | Yes | Starting Y coordinate | | `end_x` | integer | Yes | Ending X coordinate | | `end_y` | integer | Yes | Ending Y coordinate | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/drag \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"start_x": 100, "start_y": 200, "end_x": 400, "end_y": 500}' ``` --- ## Wait **`POST /api/v1/computers/{id}/desktop/wait`** Server-side pause. Holds the connection for the specified duration before responding. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `seconds` | number | No | Seconds to wait (default: 1, max: 30) | ### Response — `200 OK` ```json { "success": true, "waited_seconds": 2 } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/wait \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"seconds": 2}' ``` --- ## List Windows **`GET /api/v1/computers/{id}/desktop/windows`** Returns a list of open windows on the desktop. ### Response — `200 OK` ```json { "windows": [ { "id": "0x2200003", "title": "Terminal - user@computer", "class": "Xfce4-terminal", "x": 100, "y": 50, "width": 800, "height": 600 } ] } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/desktop/windows \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Focus Window **`POST /api/v1/computers/{id}/desktop/window/focus`** Brings a window to the foreground. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `window_id` | string | Yes | Window ID from the list windows response | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/window/focus \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"window_id": "0x2200003"}' ``` --- ## Launch Application **`POST /api/v1/computers/{id}/desktop/launch`** Launches an application by name or command. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `app` | string | Yes | Application name or command (e.g., `"firefox"`, `"xfce4-terminal"`) | ### Response — `200 OK` ```json { "success": true } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/launch \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"app": "firefox"}' ``` --- ## Cursor Position **`GET /api/v1/computers/{id}/desktop/cursor`** Returns the current mouse cursor coordinates. ### Response — `200 OK` ```json { "x": 500, "y": 300 } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/desktop/cursor \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Hotkey **`POST /api/v1/computers/{id}/desktop/hotkey`** Presses a hotkey combination. Functionally equivalent to `key` but uses a dedicated endpoint for named combinations. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `keys` | string[] | Yes | Ordered list of keys to press simultaneously (e.g. `["ctrl", "shift", "t"]`) | ### Response — `200 OK` ```json {"success": true} ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/hotkey \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"keys": ["ctrl", "shift", "t"]}' ``` --- ## Key Down / Key Up **`POST /api/v1/computers/{id}/desktop/key-down`** **`POST /api/v1/computers/{id}/desktop/key-up`** Send individual key-down or key-up events. Use these for precise control when holding a modifier key while performing another action. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `key` | string | Yes | Key name (same format as `key` endpoint) | ### Response — `200 OK` ```json {"success": true} ``` --- ## Mouse Down / Mouse Up **`POST /api/v1/computers/{id}/desktop/mouse-down`** **`POST /api/v1/computers/{id}/desktop/mouse-up`** Send individual mouse-button-down or mouse-button-up events. Use for custom drag sequences or long-press interactions. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | X coordinate | | `y` | integer | Yes | Y coordinate | | `button` | string | No | `"left"` (default), `"right"`, `"middle"` | ### Response — `200 OK` ```json {"success": true} ``` --- ## Move Mouse **`POST /api/v1/computers/{id}/desktop/move`** Moves the mouse cursor to the specified position without clicking. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | Target X coordinate | | `y` | integer | Yes | Target Y coordinate | ### Response — `200 OK` ```json {"success": true} ``` --- ## Clipboard ### Get Clipboard **`GET /api/v1/computers/{id}/desktop/clipboard`** Returns the current clipboard contents. ### Response — `200 OK` ```json {"text": "Hello, World!"} ``` ### Set Clipboard **`POST /api/v1/computers/{id}/desktop/clipboard`** Sets the clipboard contents. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `text` | string | Yes | Text to place in the clipboard | ### Response — `200 OK` ```json {"success": true} ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/desktop/clipboard \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"text": "Hello, World!"}' ``` --- ## Screen Size **`GET /api/v1/computers/{id}/desktop/screen-size`** Returns the current desktop resolution. ### Response — `200 OK` ```json {"width": 1280, "height": 720} ``` --- ## Environment **`GET /api/v1/computers/{id}/desktop/environment`** Returns desktop session metadata — display server, session type, active user, and desktop environment name. --- ## Set Wallpaper **`POST /api/v1/computers/{id}/desktop/wallpaper`** Sets the desktop wallpaper. Supports per-tenant white-label branding. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | Yes | Publicly accessible URL of the image to set as wallpaper | --- ## Accessibility Tree **`GET /api/v1/computers/{id}/desktop/accessibility-tree`** Returns the AT-SPI accessibility tree for the current desktop state. The tree describes all visible UI elements with their roles, labels, states, and bounding boxes. Use this for element discovery when exact coordinates are unknown. ### Response — `200 OK` ```json { "tree": { "role": "application", "name": "Firefox", "children": [ { "role": "button", "name": "Close", "x": 1260, "y": 10, "width": 20, "height": 20 } ] } } ``` --- ## Window Management ### List Windows See [List Windows](#list-windows) above. ### Window Size **`GET /api/v1/computers/{id}/desktop/window/{window_id}/size`** Returns the width and height of a specific window. ### Window Position **`GET /api/v1/computers/{id}/desktop/window/{window_id}/position`** Returns the x,y position of a specific window. ### Resize Window **`POST /api/v1/computers/{id}/desktop/window/{window_id}/resize`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `width` | integer | Yes | New width in pixels | | `height` | integer | Yes | New height in pixels | ### Move Window **`POST /api/v1/computers/{id}/desktop/window/{window_id}/move`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `x` | integer | Yes | New X position | | `y` | integer | Yes | New Y position | ### Maximize Window **`POST /api/v1/computers/{id}/desktop/window/{window_id}/maximize`** ### Minimize Window **`POST /api/v1/computers/{id}/desktop/window/{window_id}/minimize`** ### Close Window **`POST /api/v1/computers/{id}/desktop/window/{window_id}/close`** All window management endpoints return `{"success": true}` on `200 OK`. --- ## Common Errors All desktop endpoints share these error responses: | Status | Code | Description | |--------|------|-------------| | 400 | `invalid computer id` | UUID format invalid | | 403 | `FORBIDDEN` | Not a member of this computer | | 404 | `NOT_FOUND` | Computer does not exist | | 409 | `COMPUTER_NOT_RUNNING` | Computer is stopped or provisioning | | 502 | `AGENT_UNAVAILABLE` | Cannot reach the in-VM agent (envd) | --- ## See also - [Computers API](/docs/api-reference/computers/) — create, start, stop, clone computers - [Exec API](/docs/api-reference/exec/) — run bash/Python without desktop interaction - [Files API](/docs/api-reference/files/) — read and write files in the VM - [Error Codes](/docs/api-reference/errors/) — complete error code reference --- # Error Codes URL: https://miosa.ai/docs/api-reference/errors Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/errors Source: src/routes/docs/api-reference/errors/+page.md Description: Every error code the MIOSA API returns, what it means, and how to handle it. Every MIOSA API error follows a single envelope shape. The HTTP status indicates the broad class; the `code` field is the machine-readable reason. ```json { "error": { "code": "NOT_FOUND", "message": "sandbox not found", "request_id": "req_01jv..." } } ``` Include `request_id` whenever you contact support. It correlates the request across all internal services. --- ## Authentication errors Returned by the `ApiKeyAuth` plug before the request reaches any controller. | Code | HTTP | Meaning | What to do | |---|---|---|---| | `invalid_token` | 401 | `Authorization` header missing, malformed, or the key/JWT is invalid | Verify the header is `Authorization: Bearer msk_...` | | `invalid_token` | 401 | Refresh token used on a non-auth endpoint | Use a workspace API key (`msk_*`), not a refresh token | | `insufficient_scope` | 403 | Key exists but lacks the required scope for this operation | Adjust key scopes in the dashboard | | `rate_limit_exceeded` | 429 | Too many requests from this key or IP | Back off and retry after `Retry-After` seconds | All API endpoints are rate-limited. General API: 300 req/min per workspace. Auth endpoints: 20 req/min. Retry-After is included in 429 responses. --- ## General resource errors These codes appear across all resource types (computers, sandboxes, deployments, workspaces, etc.). | Code | HTTP | Meaning | What to do | |---|---|---|---| | `NOT_FOUND` | 404 | Resource does not exist or belongs to a different tenant | Confirm the ID and that the key's tenant owns the resource | | `FORBIDDEN` | 403 | Authenticated but not authorized to access this resource | Check membership or ownership | | `INVALID_ID` | 400 | Path parameter is not a valid UUID | Use a UUID v4 ID, not a slug or name | | `MISSING_PARAM` | 400 | A required request body field is absent | Check the field name in the request body schema | | `VALIDATION_ERROR` | 422 | One or more fields failed schema validation | Inspect `details` for per-field messages | | `VALIDATION_FAILED` | 422 | Changeset validation failed (alias of `VALIDATION_ERROR` in some controllers) | Inspect `details` | | `TENANT_RESOLUTION_FAILED` | 422 | Cannot determine the tenant for the authenticated identity | The API key may belong to a workspace that has been deleted or suspended | | `INTERNAL_ERROR` | 500 | Unexpected server-side error | Retry with exponential backoff; report if persistent | --- ## Sandbox errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `INSUFFICIENT_CREDITS` | 402 | Not enough credits to provision this sandbox | Top up credits in the dashboard or reduce the resource request | | `SANDBOX_LIMIT_REACHED` | 409 | Tenant has hit the concurrent sandbox cap (default 10) | Destroy idle sandboxes or contact support to raise the limit | | `SANDBOX_NOT_RUNNING` | 409 | Operation requires the sandbox to be in `running` state | Wait until state is `running`; poll `GET /sandboxes/{id}` or subscribe to events | | `AGENT_UNAVAILABLE` | 502 | The in-VM envd agent is reachable but not responding | Wait and retry; if persistent, destroy and recreate the sandbox | | `INVALID_TEMPLATE` | 400 | `template_id` is not a recognized template | Use `GET /api/v1/sandbox-templates` to list valid IDs | | `MISSING_PATH` | 400 | `path` field is required but absent | Include `path` in the request body | | `INVALID_PATH` | 400 | `path` is not a valid absolute filesystem path | Use an absolute path (starts with `/`) | | `MISSING_FILE` | 400 | Multipart upload missing the `file` part | Include the file part in the multipart form | | `INVALID_BODY` | 400 | Request body could not be parsed or decoded | Verify JSON or base64 encoding | | `MISSING_MODE` | 400 | `chmod` call missing `mode` field | Include an octal mode string, e.g. `"0644"` | | `INVALID_PORT` | 422 | Expose port is outside `1`–`65535` | Use a port in the valid range | | `MISSING_PARAM` | 400 | `port` omitted on `/expose` and template has no default preview port | Provide `port` explicitly | | `SANDBOX_NOT_RUNNING` | 409 | `/deploy` called but sandbox is not running | Confirm sandbox state before promoting | | `SANDBOX_RUNTIME_UNAVAILABLE` | 409 | Sandbox is marked running but has no VM IP yet | Retry after a few seconds | --- ## Sandbox template build errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `TEMPLATE_NOT_FOUND` | 404 | Template ID does not exist | Verify the template ID | | `BUILD_NOT_FOUND` | 404 | Build record does not exist | Verify the build ID | | `BUILD_ALREADY_TERMINAL` | 409 | Cannot cancel a build that is already `ready`, `failed`, or `cancelled` | No action needed; build is in a terminal state | | `BUILD_NOT_RETRYABLE` | 409 | Build is not in a state that allows retry (`failed` or `cancelled` only) | Only `failed` or `cancelled` builds may be retried | | `UNAUTHORIZED` | 401 | SSE ticket missing, invalid, or expired | Re-issue a ticket via `POST /auth/sse-ticket` and reconnect | --- ## Computer errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `INSUFFICIENT_CREDITS` | 402 | Not enough credits to start this computer | Top up credits | | `COMPUTER_NOT_RUNNING` | 409 | Desktop or exec operation requires a running computer | Start the computer first | | `COMPUTER_NOT_FOUND` | 404 | Computer does not exist in this tenant | Verify the computer ID | | `AGENT_UNAVAILABLE` | 502 | In-VM envd agent unreachable | Computer may be starting; retry after a few seconds | --- ## Deployment errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `DEPLOYMENT_NOT_FOUND` | 404 | Deployment ID does not exist | Verify the deployment ID | | `VALIDATION_ERROR` | 422 | Deployment fields failed validation | Check `details` for field-level messages | | `SANDBOX_NOT_RUNNING` | 409 | Publishing requires a running sandbox | Confirm sandbox state | | `RUNTIME_TARGET_UNAVAILABLE` | 409 | Sandbox has no IP yet — cannot route to it | Retry after a few seconds | | `RUNTIME_LOGS_UNAVAILABLE` | 502 | Cannot fetch runtime logs from the sandbox | Retry or check sandbox health | | `EMPTY_ARTIFACT` | 422 | Publish output contained no files | Verify your build step produces output files | | `SERVICE_UNAVAILABLE` | 503 | A required internal service (e.g. encryption) is not configured | Contact support | --- ## Snapshot errors Snapshots are accessed under `/api/v1/computers/{id}/snapshots` and `/api/v1/sandboxes/{id}/snapshots`. | Code | HTTP | Meaning | What to do | |---|---|---|---| | `NOT_FOUND` (snapshot) | 404 | Snapshot does not exist or belongs to a different computer | Verify snapshot ID and computer ID | | `FORBIDDEN` | 403 | Authenticated tenant does not own this snapshot | Check ownership | | `already_deleted` | 409 | Snapshot has already been deleted | No action; already in terminal state | | `snapshot is not in ready state` | 409 | Restore attempted before snapshot reached `ready` | Poll snapshot status until `ready` | | `missing sse ticket` | 401 | SSE stream opened without a ticket | Issue a ticket via `POST /auth/sse-ticket` first | --- ## Custom domain errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `DOMAIN_NOT_FOUND` | 404 | Custom domain record not found | Verify the domain ID | | `COMPUTER_NOT_FOUND` | 404 | The computer this domain is attached to was not found | Verify the computer ID | | `FORBIDDEN` | 403 | Domain belongs to a different tenant | Verify ownership | | `VALIDATION_FAILED` | 422 | `fqdn` missing or invalid | Include a valid fully-qualified domain name | --- ## Workspace errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `VALIDATION_FAILED` | 422 | Workspace creation or update fields are invalid | Inspect `details` | | `TEMPLATE_NAME_TAKEN` | 409 | A template with that name already exists in this workspace | Choose a different name | --- ## Rate-limit and quota errors | Code | HTTP | Meaning | What to do | |---|---|---|---| | `rate_limit_exceeded` | 429 | General API rate limit hit | Respect the `Retry-After` response header | | `SANDBOX_LIMIT_REACHED` | 409 | Concurrent sandbox cap reached | Destroy idle sandboxes or upgrade plan | | `INSUFFICIENT_CREDITS` | 402 | Credit balance depleted | Purchase credits from the billing dashboard | All 429 responses include a `Retry-After` header (seconds). Honor it — repeated violations may result in temporary IP blocks. --- ## See also - [API Reference overview](/docs/api-reference/) — base URL, auth, and request format - [Sandboxes](/docs/api-reference/sandboxes/) — sandbox lifecycle and file operations - [Computers](/docs/api-reference/computers/) — computer CRUD and desktop control - [Deployments](/docs/api-reference/deployments/) — deployment and version errors --- # Events (SSE) URL: https://miosa.ai/docs/api-reference/events Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/events Source: src/routes/docs/api-reference/events/+page.md Description: Server-Sent Event streams for real-time updates from sandboxes, computers, deployments, and builds. MIOSA exposes SSE streams for resources that produce timed events: sandboxes, computer sessions, deployment builds, sandbox-template builds, and computer logs. All streams follow the same authentication and envelope pattern. --- ## Authentication pattern The browser `EventSource` API cannot send an `Authorization` header. Use the two-step ticket flow for all SSE connections: Diagram: sequenceDiagram participant Client participant MIOSA API Client->>MIOSA API: POST /api/v1/auth/sse-ticket
Authorization: Bearer msk_u_... MIOSA API-->>Client: { "ticket": "sset_..." } Client->>MIOSA API: GET ?ticket=sset_...
Accept: text/event-stream MIOSA API-->>Client: text/event-stream (open connection) **Step 1 — Mint a ticket:** ```bash curl -X POST https://api.miosa.ai/api/v1/auth/sse-ticket \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" # Response: { "ticket": "sset_01hx9z...", "expires_in": 60 } ``` Tickets are single-use and expire in 60 seconds. Mint one immediately before opening the `EventSource`. **Step 2 — Open the stream:** ```bash curl -N "https://api.miosa.ai/api/v1/sandboxes/{id}/events?ticket=sset_01hx9z..." \ -H "Accept: text/event-stream" ``` --- ## Live SSE streams | Stream | Endpoint | Description | |---|---|---| | Sandbox events | `GET /sandboxes/{id}/events` | Exec progress, file change notifications, preview readiness | | Computer agent events | `GET /computers/{id}/agent/events` | Agent task progress and computer lifecycle | | Computer logs | `GET /computers/{id}/logs/stream` | stdout/stderr from in-VM agent processes | | Sandbox logs | `GET /sandboxes/{id}/logs/stream` | stdout/stderr from sandbox template lifecycle | | Build events | `GET /sandbox-template-builds/{id}/logs/stream` | Custom template build progress | --- ## Event envelope All events arrive in standard SSE format: ``` event: data: id: ``` All payloads are JSON. The `id:` line allows clients to resume with `Last-Event-ID` on reconnect. --- ## Sandbox event types | Event | Payload | Description | |---|---|---| | `sandbox.started` | `{sandbox_id, region}` | Boot complete; ready to accept exec | | `sandbox.exec.started` | `{exec_id, command}` | Command began | | `sandbox.exec.stdout` | `{exec_id, line}` | stdout chunk | | `sandbox.exec.stderr` | `{exec_id, line}` | stderr chunk | | `sandbox.exec.completed` | `{exec_id, exit_code, duration_ms}` | Command exited | | `sandbox.preview.ready` | `{preview_id, url, port}` | Preview URL available | | `sandbox.idle` | `{sandbox_id, last_activity_at}` | Going to auto-suspend | | `sandbox.suspended` | `{sandbox_id}` | Auto-suspended after idle window | --- ## Deployment event types | Event | Payload | Description | |---|---|---| | `deployment.publish.queued` | `{version_id}` | Publish accepted, waiting for builder | | `deployment.publish.building` | `{version_id, stage}` | Builder running | | `deployment.publish.uploading` | `{version_id, size_bytes}` | Release artifact upload | | `deployment.publish.promoting` | `{version_id}` | Switching active version | | `deployment.publish.completed` | `{version_id, deployment_id, url}` | Live | | `deployment.publish.failed` | `{version_id, error_code, message}` | Build or health-check failure | --- ## Computer event types | Event | Payload | Description | |---|---|---| | `computer.started` | `{computer_id, region}` | VM booted, desktop ready | | `computer.stream.token_ready` | `{token, expires_at}` | New short-lived stream credential | | `computer.idle` | `{computer_id, idle_seconds}` | Approaching auto-stop | | `computer.stopped` | `{computer_id, reason}` | Stopped (manual or auto) | --- ## Reconnect and replay Send `Last-Event-ID: <ULID>` on the resubscribe request. The server replays all events since that ID. Events are retained for **15 minutes** per resource. ```bash curl -N "https://api.miosa.ai/api/v1/sandboxes/{id}/events?ticket=sset_..." \ -H "Accept: text/event-stream" \ -H "Last-Event-ID: 01hwqz4b3c2v1x..." ``` --- ## Error handling If the connection drops or the ticket expires, the next request returns `401`. Mint a fresh ticket and resubscribe: ```typescript async function openStream(sandboxId: string): Promise { const { ticket } = await fetch('/api/v1/auth/sse-ticket', { method: 'POST', headers: { Authorization: `Bearer ${process.env.MIOSA_API_KEY}` }, }).then(r => r.json()); const es = new EventSource( `https://api.miosa.ai/api/v1/sandboxes/${sandboxId}/events/stream?ticket=${ticket}` ); es.addEventListener('error', () => { es.close(); setTimeout(() => openStream(sandboxId), 1000); }); es.addEventListener('sandbox.exec.completed', (e) => { console.log('Exec done:', JSON.parse(e.data)); }); } ``` --- ## See also Create sandboxes, run exec, write files, open previews. [Sandboxes →](/docs/develop/sandboxes/) Builder, Release, Version — the full deploy pipeline. [Publishing →](/docs/deploy/publishing/) Desktop lifecycle, screenshot, click, keyboard APIs. [Computers →](/docs/computers/overview/) API keys, browser tokens, SSE tickets. [Authentication →](/docs/authentication/) --- # Exec API URL: https://miosa.ai/docs/api-reference/exec Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/exec Source: src/routes/docs/api-reference/exec/+page.md Description: API reference for executing bash commands and Python code on MIOSA computers. Run commands and scripts directly on a computer without desktop interaction. Both endpoints block until the command exits and return combined stdout+stderr. Base path: `/api/v1/computers/{id}/exec` The computer must be in `"running"` status. Commands run as the default user inside the VM. Maximum timeout is 300 s. Rate limit: 300 req/min per workspace. For long-running output, prefer the streaming exec endpoint which avoids holding an HTTP connection open for the full duration. --- ## Execute Bash Command **`POST /api/v1/computers/{id}/exec`** Runs a shell command on the computer. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `command` | string | Yes | Shell command to execute | | `timeout` | integer | No | Timeout in seconds (default: 30, max: 300) | ### Response — `200 OK` ```json { "output": "total 24\ndrwxr-xr-x 6 user user 4096 Apr 11 10:00 .\n", "exit_code": 0 } ``` The `output` field contains combined stdout and stderr. ### Errors | Status | Code | Description | |--------|------|-------------| | 409 | `COMPUTER_NOT_RUNNING` | Computer is not running | | 502 | — | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "ls -la /home/user", "timeout": 30}' ``` ### Examples ```bash # Install a package curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "sudo apt-get install -y jq", "timeout": 120}' # Check disk space curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "df -h"}' # Multi-line script curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "cd /home/user && mkdir -p project && echo done"}' ``` --- ## Execute Python Code **`POST /api/v1/computers/{id}/exec/python`** Runs Python code on the computer. The code is written to a temporary file and executed with `python3`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `code` | string | Yes | Python source code | | `timeout` | integer | No | Timeout in seconds (default: 30, max: 300) | ### Response — `200 OK` ```json { "output": "42\n", "exit_code": 0 } ``` ### How It Works 1. Code is written to `/tmp/miosa_exec_<random>.py` 2. Executed via `timeout <N> python3 /tmp/miosa_exec_<random>.py 2>&1` 3. Temporary file is deleted after execution 4. stdout and stderr are captured in `output` ### Errors | Status | Code | Description | |--------|------|-------------| | 409 | `COMPUTER_NOT_RUNNING` | Computer is not running | | 502 | — | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/python \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "import sys\nprint(f\"Python {sys.version}\")\nprint(2 + 2)", "timeout": 30 }' ``` ### Examples ```bash # JSON processing curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/python \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "import json\ndata = {\"hello\": \"world\"}\nprint(json.dumps(data, indent=2))", "timeout": 10 }' # File processing curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/python \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "import os\nfor f in os.listdir(\"/home/user\"):\n print(f)", "timeout": 10 }' # Long-running computation curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/python \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "code": "total = sum(range(10_000_000))\nprint(f\"Sum: {total}\")", "timeout": 60 }' ``` --- ## Terminal ### Create Terminal Session **`POST /api/v1/computers/{id}/terminal`** Creates a WebSocket-based terminal session on the computer. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `cols` | integer | No | Terminal columns (default: 80) | | `rows` | integer | No | Terminal rows (default: 24) | | `shell` | string | No | Shell to use (default: `/bin/bash`) | #### Response — `201 Created` ```json { "data": { "session_id": "default", "ws_url": "wss://my-computer.sandbox.miosa.ai/ws/terminal/550e8400-.../default?auth=stream_token", "stream_auth": "stream_token", "expires_at": 1712700060, "computer_id": "550e8400-e29b-41d4-a716-446655440000" } } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/terminal \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"cols": 80, "rows": 24, "shell": "/bin/bash"}' ``` --- ### Resize Terminal **`POST /api/v1/computers/{id}/pty/{session_id}/resize`** Resizes an active terminal session. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `cols` | integer | Yes | New column count | | `rows` | integer | Yes | New row count | #### Response — `200 OK` ```json { "status": "ok" } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/pty/{session_id}/resize \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"cols": 120, "rows": 40}' ``` --- ## Timeout Behavior - Commands exceeding the timeout are killed with SIGTERM - The maximum timeout is **300 seconds** (5 minutes) - If no timeout is specified, the default is **30 seconds** - The timeout value is clamped: `min(requested, 300)` ## Pre-installed Software The default template includes: - Python 3 with pip - Node.js (if installed via selected_apps) - Common CLI tools: curl, wget, git, vim, jq - System utilities: htop, tree, zip/unzip Install additional packages via the bash exec endpoint: ```bash # Python packages {"command": "pip install requests pandas numpy"} # System packages {"command": "sudo apt-get install -y postgresql-client"} ``` --- ## See also - [Streaming Exec](/docs/api-reference/streaming-exec/) — SSE stream for long-running commands - [Computers API](/docs/api-reference/computers/) — computer CRUD and lifecycle - [Files API](/docs/api-reference/files/) — read and write files without exec - [Error Codes](/docs/api-reference/errors/) — `COMPUTER_NOT_RUNNING`, `AGENT_UNAVAILABLE` --- # Files API URL: https://miosa.ai/docs/api-reference/files Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/files Source: src/routes/docs/api-reference/files/+page.md Description: API reference for file upload, download, list, export, and delete operations. Manage files on a running computer. All paths are restricted to `/home/user`, `/home/ubuntu`, and `/tmp`. Base path: `/api/v1/computers/{id}/files` The computer must be in `"running"` status. All operations go through the in-VM agent (envd). --- ## Upload a File **`POST /api/v1/computers/{id}/files/upload`** Uploads a file to the computer via multipart form data. ### Request Content-Type: `multipart/form-data` | Field | Type | Required | Description | |-------|------|----------|-------------| | `file` | file | Yes | File to upload | | `path` | string | No | Remote path. If ends with `/`, filename is appended. Default: `/home/user/` | ### Response — `201 Created` ```json { "success": true, "file": { "path": "/home/user/script.py", "filename": "script.py", "size_bytes": 1234 } } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_FILE` | No file field in request | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 413 | `FILE_TOO_LARGE` | File exceeds 10 MB limit | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/upload \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -F "file=@./script.py" \ -F "path=/home/user/scripts/" ``` --- ## List Directory **`GET /api/v1/computers/{id}/files`** Lists files and directories at a given path. ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `path` | string | No | Directory to list. Default: `/home/user` | ### Response — `200 OK` ```json { "files": [ { "name": "Documents", "type": "directory", "size_bytes": 4096, "modified_at": "2026-04-11T10:30:00Z" }, { "name": "script.py", "type": "file", "size_bytes": 1234, "modified_at": "2026-04-11T10:25:00Z" } ], "path": "/home/user" } ``` ### File Entry Fields | Field | Type | Description | |-------|------|-------------| | `name` | string | File or directory name | | `type` | string | `"file"` or `"directory"` | | `size_bytes` | integer | Size in bytes | | `modified_at` | ISO 8601 | Last modification time | ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files?path=/home/user" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Download a File **`GET /api/v1/computers/{id}/files/download`** Downloads a file from the computer as binary data. ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `path` | string | Yes | Full path to the file | ### Response — `200 OK` Binary file content. Content-Type is inferred from the file extension. The `Content-Disposition` header is set for download. ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_PATH` | No path parameter | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files/download?path=/home/user/output.txt" \ -H "Authorization: Bearer $MIOSA_API_KEY" \ --output output.txt ``` --- ## Export a File **`POST /api/v1/computers/{id}/files/export`** Returns file metadata and base64-encoded content in a single response. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Full path to the file | ### Response — `200 OK` ```json { "success": true, "file": { "path": "/home/user/data.json", "filename": "data.json", "size_bytes": 5678, "content_type": "application/json", "modified_at": "2026-04-11T10:30:00Z" }, "content_base64": "eyJrZXkiOiAidmFsdWUifQ==" } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_PATH` | No path in request body | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 404 | `FILE_NOT_FOUND` | File does not exist or is empty | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/export \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/data.json"}' ``` --- ## Delete a File **`DELETE /api/v1/computers/{id}/files`** Removes a file from the computer. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Full path to the file to delete | ### Response — `200 OK` ```json { "success": true, "path": "/home/user/old-file.txt" } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_PATH` | No path in request body | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/files \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/old-file.txt"}' ``` --- ## Path Security All paths are validated before execution: 1. **Expansion** — `Path.expand` resolves `..`, `~`, and symlinks 2. **Prefix check** — Expanded path must start with `/home/user`, `/home/ubuntu`, or `/tmp` 3. **Rejection** — Paths outside these prefixes return `403 FORBIDDEN_PATH` This prevents path traversal attacks. For example, `/home/user/../../etc/passwd` expands to `/etc/passwd` and is rejected. --- # Filesystem API URL: https://miosa.ai/docs/api-reference/filesystem Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/filesystem Source: src/routes/docs/api-reference/filesystem/+page.md Description: Complete API reference for file and directory operations on MIOSA computers — upload, download, stat, mkdir, rename, copy, chmod, and readdir. The Filesystem API gives you full programmatic access to files and directories inside a running computer. All operations are proxied through the in-VM envd daemon and restricted to allowed paths. Base path: `/api/v1/computers/{id}/files` **Allowed paths:** `/home/user`, `/home/ubuntu`, `/tmp` The computer must be in `"running"` status. Paths outside the allowed prefixes return `403 FORBIDDEN_PATH`. --- ## Quick Start ```typescript const client = new Miosa(); // Write a file await client.files.write(computerId, { path: '/home/user/hello.py', content: 'print("Hello from MIOSA")', }); // List the directory const { files } = await client.files.list(computerId, '/home/user'); // Download it back const content = await client.files.download(computerId, '/home/user/hello.py'); ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/files/upload` | Upload a file (multipart) | | `POST` | `/files/write` | Write text/binary content directly | | `GET` | `/files` | List directory (`readdir`) | | `GET` | `/files/download` | Download a file | | `POST` | `/files/export` | Download with metadata (base64) | | `DELETE` | `/files` | Delete a file | | `GET` | `/files/stat` | Stat a file or directory | | `POST` | `/files/mkdir` | Create a directory | | `POST` | `/files/rename` | Rename or move a file/directory | | `POST` | `/files/copy` | Copy a file | | `POST` | `/files/chmod` | Change file permissions | --- ## Upload a File **`POST /api/v1/computers/{id}/files/upload`** Multipart upload — use this for binary files or large payloads. ### Request — `multipart/form-data` | Field | Type | Required | Description | |-------|------|----------|-------------| | `file` | file | Yes | File to upload | | `path` | string | No | Remote path. If ends with `/`, filename is appended. Default: `/home/user/` | ### Response — `201 Created` ```json { "success": true, "file": { "path": "/home/user/script.py", "filename": "script.py", "size_bytes": 1234 } } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_FILE` | No file field in request | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 413 | `FILE_TOO_LARGE` | File exceeds 10 MB limit | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/upload \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -F "file=@./script.py" \ -F "path=/home/user/scripts/" ``` --- ## Write a File **`POST /api/v1/computers/{id}/files/write`** Write text or base64-encoded binary content. Faster than multipart for programmatic use. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Full destination path | | `content` | string | Yes | File content (UTF-8 text or base64 when `encoding="base64"`) | | `encoding` | string | No | `"utf8"` (default) or `"base64"` | ### Response — `201 Created` ```json { "success": true, "file": { "path": "/home/user/config.json", "size_bytes": 256 } } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/write \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/config.json", "content": "{\"key\": \"value\"}"}' ``` --- ## List Directory (readdir) **`GET /api/v1/computers/{id}/files`** ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `path` | string | No | Directory to list. Default: `/home/user` | ### Response — `200 OK` ```json { "files": [ { "name": "Documents", "type": "directory", "size_bytes": 4096, "permissions": "drwxr-xr-x", "modified_at": "2026-04-11T10:30:00Z" }, { "name": "script.py", "type": "file", "size_bytes": 1234, "permissions": "-rw-r--r--", "modified_at": "2026-04-11T10:25:00Z" } ], "path": "/home/user" } ``` ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files?path=/home/user" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Download a File **`GET /api/v1/computers/{id}/files/download`** ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `path` | string | Yes | Full path to the file | ### Response — `200 OK` Binary file content. `Content-Disposition` is set for browser download. ### Errors | Status | Code | Description | |--------|------|-------------| | 400 | `MISSING_PATH` | No path parameter | | 403 | `FORBIDDEN_PATH` | Path outside allowed directories | | 404 | `FILE_NOT_FOUND` | File does not exist | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files/download?path=/home/user/output.txt" \ -H "Authorization: Bearer $MIOSA_API_KEY" \ --output output.txt ``` --- ## Export a File **`POST /api/v1/computers/{id}/files/export`** Returns metadata and base64-encoded content in a single JSON response. Useful when you need the file and its metadata together without handling binary responses. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Full path to the file | ### Response — `200 OK` ```json { "success": true, "file": { "path": "/home/user/data.json", "filename": "data.json", "size_bytes": 5678, "content_type": "application/json", "modified_at": "2026-04-11T10:30:00Z" }, "content_base64": "eyJrZXkiOiAidmFsdWUifQ==" } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/export \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/data.json"}' ``` --- ## Delete a File **`DELETE /api/v1/computers/{id}/files`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Full path to delete | ### Response — `200 OK` ```json { "success": true, "path": "/home/user/old-file.txt" } ``` ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/files \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/old-file.txt"}' ``` --- ## Stat a File or Directory **`GET /api/v1/computers/{id}/files/stat`** Returns metadata without downloading file content. ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `path` | string | Yes | Full path | ### Response — `200 OK` ```json { "path": "/home/user/script.py", "type": "file", "size_bytes": 1234, "permissions": "-rw-r--r--", "owner": "user", "group": "user", "modified_at": "2026-04-11T10:25:00Z", "created_at": "2026-04-11T09:00:00Z" } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 404 | `FILE_NOT_FOUND` | Path does not exist | ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files/stat?path=/home/user/script.py" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Create a Directory **`POST /api/v1/computers/{id}/files/mkdir`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Directory path to create | | `recursive` | boolean | No | Create parent directories if missing (default: `false`) | ### Response — `201 Created` ```json { "success": true, "path": "/home/user/project/src" } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/mkdir \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/project/src", "recursive": true}' ``` --- ## Rename or Move **`POST /api/v1/computers/{id}/files/rename`** Renames a file or directory, or moves it to a different path. Both source and destination must be within allowed paths. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `source` | string | Yes | Current path | | `destination` | string | Yes | Target path | ### Response — `200 OK` ```json { "success": true, "source": "/home/user/old.txt", "destination": "/home/user/new.txt" } ``` ### Errors | Status | Code | Description | |--------|------|-------------| | 404 | `FILE_NOT_FOUND` | Source path does not exist | | 409 | `DESTINATION_EXISTS` | Destination already exists | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/rename \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"source": "/home/user/app.py", "destination": "/home/user/app_v2.py"}' ``` --- ## Copy a File **`POST /api/v1/computers/{id}/files/copy`** Copies a file to a new path. Directory copy is not supported — copy individual files. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `source` | string | Yes | Source file path | | `destination` | string | Yes | Destination file path | ### Response — `201 Created` ```json { "success": true, "source": "/home/user/template.py", "destination": "/home/user/project/main.py" } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/copy \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"source": "/home/user/template.py", "destination": "/home/user/project/main.py"}' ``` --- ## Change Permissions **`POST /api/v1/computers/{id}/files/chmod`** Sets UNIX file permissions. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | File path | | `mode` | string | Yes | Octal permission string, e.g. `"755"` or `"644"` | ### Response — `200 OK` ```json { "success": true, "path": "/home/user/script.sh", "mode": "755" } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/files/chmod \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path": "/home/user/script.sh", "mode": "755"}' ``` --- ## Path Security All paths are validated before execution: 1. **Expansion** — `Path.expand` resolves `..`, `~`, and symlinks 2. **Prefix check** — Must start with `/home/user`, `/home/ubuntu`, or `/tmp` 3. **Rejection** — Paths outside these prefixes return `403 FORBIDDEN_PATH` `/home/user/../../etc/passwd` expands to `/etc/passwd` and is rejected. --- ## Common Recipes ### Scaffold a project directory ```typescript // Create structure await client.files.mkdir(computerId, { path: '/home/user/app/src', recursive: true }); await client.files.mkdir(computerId, { path: '/home/user/app/tests', recursive: true }); // Write files await client.files.write(computerId, { path: '/home/user/app/src/main.py', content: mainPy }); await client.files.write(computerId, { path: '/home/user/app/requirements.txt', content: reqs }); // Make entrypoint executable await client.files.chmod(computerId, { path: '/home/user/app/src/main.py', mode: '755' }); ``` ### Retrieve build artifacts ```python # List the build output directory files = client.files.list(computer_id, path="/home/user/app/dist") for f in files["files"]: if f["type"] == "file": content = client.files.export(computer_id, path=f"/home/user/app/dist/{f['name']}") save_artifact(f["name"], content["content_base64"]) ``` --- # Network Policy API URL: https://miosa.ai/docs/api-reference/network-policy Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/network-policy Source: src/routes/docs/api-reference/network-policy/+page.md Description: API reference for configuring per-computer network access controls on MIOSA computers. Network policies control what external hosts and ports a computer can reach. Use them to sandbox untrusted workloads, enforce egress restrictions, or prevent AI agents from making unexpected outbound connections. Base path: `/api/v1/computers/{id}/network-policy` Network policies apply at the Firecracker VM level. Changing a policy takes effect within seconds without restarting the computer. --- ## Quick Start ```typescript const client = new Miosa(); // Restrict to only allow HTTPS to GitHub and PyPI await client.networkPolicy.update(computerId, { mode: 'allowlist', rules: [ { host: 'github.com', port: 443, protocol: 'tcp' }, { host: 'pypi.org', port: 443, protocol: 'tcp' }, { host: 'files.pythonhosted.org', port: 443, protocol: 'tcp' }, ], }); ``` ```bash curl -X PUT https://api.miosa.ai/api/v1/computers/{id}/network-policy \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "allowlist", "rules": [ { "host": "github.com", "port": 443, "protocol": "tcp" } ] }' ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/computers/{id}/network-policy` | Get current network policy | | `PUT` | `/computers/{id}/network-policy` | Set network policy (replaces existing) | | `DELETE` | `/computers/{id}/network-policy` | Reset to default (unrestricted) | --- ## Get Network Policy **`GET /api/v1/computers/{id}/network-policy`** ### Response — `200 OK` ```json { "data": { "computer_id": "...", "mode": "unrestricted", "rules": [], "updated_at": "2026-04-11T00:00:00Z" } } ``` ### Policy Modes | Mode | Description | |------|-------------| | `unrestricted` | Default. All outbound traffic allowed | | `allowlist` | Only listed hosts/ports permitted | | `denylist` | All traffic permitted except listed rules | | `isolated` | All outbound blocked (DNS and MIOSA internal excluded) | ```bash curl https://api.miosa.ai/api/v1/computers/{id}/network-policy \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Set Network Policy **`PUT /api/v1/computers/{id}/network-policy`** Replaces the entire policy. Partial updates are not supported — send all rules on every PUT. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `mode` | string | Yes | `"unrestricted"`, `"allowlist"`, `"denylist"`, or `"isolated"` | | `rules` | array | No | Required when mode is `"allowlist"` or `"denylist"` | #### Rule Object | Field | Type | Required | Description | |-------|------|----------|-------------| | `host` | string | Yes | Hostname or CIDR block (e.g. `10.0.0.0/8`) | | `port` | integer | No | Port number. Omit to match all ports | | `protocol` | string | No | `"tcp"`, `"udp"`, or `"any"` (default) | ### Response — `200 OK` Updated policy object. ### Errors | Status | Error | Cause | |--------|-------|-------| | 400 | `rules required for allowlist/denylist mode` | Empty rules for a filtering mode | | 404 | `computer not found` | Computer does not exist | ```bash # Full isolation except for GitHub curl -X PUT https://api.miosa.ai/api/v1/computers/{id}/network-policy \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "allowlist", "rules": [ { "host": "github.com", "port": 443, "protocol": "tcp" }, { "host": "objects.githubusercontent.com", "port": 443, "protocol": "tcp" } ] }' ``` --- ## Reset to Default **`DELETE /api/v1/computers/{id}/network-policy`** Removes any custom policy. The computer reverts to unrestricted outbound access. ### Response — `200 OK` ```json { "data": { "computer_id": "...", "mode": "unrestricted" } } ``` ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/network-policy \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Common Recipes ### Sandbox an untrusted AI task ```python # Lock down before running untrusted code client.network_policy.update(computer_id, mode="isolated") # Run the task result = client.exec(computer_id, command="python3 /home/user/untrusted.py") # Restore after client.network_policy.delete(computer_id) ``` ### Allow only package registries for a build environment ```typescript const packageHosts = [ { host: 'registry.npmjs.org', port: 443, protocol: 'tcp' as const }, { host: 'pypi.org', port: 443, protocol: 'tcp' as const }, { host: 'files.pythonhosted.org', port: 443, protocol: 'tcp' as const }, { host: 'pkg.go.dev', port: 443, protocol: 'tcp' as const }, { host: 'proxy.golang.org', port: 443, protocol: 'tcp' as const }, ]; await client.networkPolicy.update(computerId, { mode: 'allowlist', rules: packageHosts, }); ``` ### Block a known malicious IP range ```typescript await client.networkPolicy.update(computerId, { mode: 'denylist', rules: [ { host: '185.220.0.0/16' }, ], }); ``` --- # OpenComputers API URL: https://miosa.ai/docs/api-reference/open-computers Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/open-computers Source: src/routes/docs/api-reference/open-computers/+page.md Description: Register and control your own machines (BYOC) via the MIOSA API — exec, files, tunnels, AI agents, inference clusters, and secrets. OpenComputers lets you register physical or virtual machines you already own (Mac, Linux, Windows) and control them through MIOSA's API — run commands, manage files, expose HTTP tunnels, dispatch AI agents, build inference clusters, and manage secrets. Base path: `/api/v1/opencomputers` Verbs supported: **GET** (list/show), **POST** (register/create/exec/dispatch), **PATCH** (update tags/tunnels), **DELETE** (revoke/cancel). Rate limit: 300 req/min per workspace. Exec jobs and agent sessions run asynchronously — poll or stream for results rather than holding the connection. OpenComputers hosts are registered by installing the `miosa-host` agent on the target machine. The **host key** returned on registration is shown exactly once — store it securely. --- ## Hosts ### List hosts **`GET /api/v1/opencomputers/hosts`** | Parameter | Type | Description | |-----------|------|-------------| | `page` | integer | Page number (default: 1) | | `per_page` | integer | Items per page (default: 20, max: 100) | ```json { "data": [ { "id": "host_abc123", "name": "my-mac", "region": "us-east", "status": "online", "tenant_id": "t_abc", "labels": { "env": "prod" }, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ], "meta": { "total": 1, "page": 1, "per_page": 20 } } ``` **Host statuses:** `pending` | `online` | `offline` | `error` | `revoked` --- ### Register a host **`POST /api/v1/opencomputers/hosts`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Display name for the host | | `region` | string | No | Region label | | `labels` | object | No | Key-value metadata | ```json { "id": "host_abc123", "name": "my-mac", "host_key": "hk_xxxxxxxxxxxxxxxx", "status": "pending", "tenant_id": "t_abc", "labels": {}, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ``` `host_key` is returned **only once** at registration time. The host agent uses it to authenticate. Store it immediately — it cannot be retrieved later. --- ### Get a host **`GET /api/v1/opencomputers/hosts/{id}`** --- ### Revoke a host **`DELETE /api/v1/opencomputers/hosts/{id}`** Returns `204 No Content`. The host agent will no longer be able to authenticate. --- ### Host event stream (SSE) **`GET /api/v1/opencomputers/hosts/{id}/events`** Streams real-time lifecycle events from the host as Server-Sent Events. ``` event: status_change data: {"type":"status_change","host_id":"host_abc","data":{"status":"online"},"timestamp":"..."} ``` --- ## Jobs Run shell commands on a registered host and retrieve output. ### Run a job **`POST /api/v1/opencomputers/hosts/{id}/exec`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `command` | string | Yes | Command to execute | | `args` | string[] | No | Arguments (passed separately from shell expansion) | | `env` | string[] | No | Environment variables in `KEY=VALUE` format | | `cwd` | string | No | Working directory | | `timeout` | integer | No | Timeout in seconds (default: 60) | ```json { "id": "job_xyz", "host_id": "host_abc123", "status": "completed", "command": "npm test", "args": [], "exit_code": 0, "stdout": "All tests passed.\n", "stderr": "", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", "completed_at": "2026-01-01T00:00:05Z" } ``` **Job statuses:** `queued` | `running` | `completed` | `failed` | `cancelled` --- ### List jobs **`GET /api/v1/opencomputers/hosts/{id}/exec`** --- ### Get a job **`GET /api/v1/opencomputers/hosts/{id}/exec/{job_id}`** --- ### Stream job output (SSE) **`GET /api/v1/opencomputers/hosts/{id}/exec/{job_id}/stream`** Streams stdout/stderr in real time while the job is running. --- ### Cancel a job **`DELETE /api/v1/opencomputers/hosts/{id}/exec/{job_id}`** Returns `204 No Content`. --- ## File System Manage files and directories on a registered host. | Endpoint | Method | Description | |----------|--------|-------------| | `/opencomputers/hosts/{id}/fs/list` | GET | List directory (`?path=`) | | `/opencomputers/hosts/{id}/fs/stat` | GET | Stat a path (`?path=`) | | `/opencomputers/hosts/{id}/fs/download` | GET | Download a file (`?path=`) | | `/opencomputers/hosts/{id}/fs/upload` | POST | Upload a file (multipart, `?path=`) | | `/opencomputers/hosts/{id}/fs/delete` | DELETE | Delete file/dir (`?path=`) | | `/opencomputers/hosts/{id}/fs/mkdir` | POST | Create directory (`{"path":"..."}`) | **List response:** ```json { "path": "/home/user/projects", "entries": [ { "name": "my-app", "path": "/home/user/projects/my-app", "size": 0, "is_dir": true, "modified_at": "2026-01-01T00:00:00Z" } ] } ``` --- ## Terminal ### Issue a terminal ticket **`POST /api/v1/opencomputers/hosts/{id}/terminal/ticket`** Returns a short-lived WebSocket authentication ticket. Connect to `ws_url` immediately using the ticket as a query parameter. ```json { "ticket": "tk_abc123", "ws_url": "wss://api.miosa.ai/opencomputers/ws/terminal?ticket=tk_abc123", "expires_at": "2026-01-01T00:00:30Z" } ``` --- ## Desktop (VNC) ### Issue a desktop ticket **`POST /api/v1/opencomputers/hosts/{id}/desktop/ticket`** Same shape as the terminal ticket. Connect to `ws_url` with the ticket to start a VNC session. --- ## Tunnels Expose local ports on the host over MIOSA-managed public URLs. ### List tunnels **`GET /api/v1/opencomputers/hosts/{id}/tunnels`** ### Create a tunnel **`POST /api/v1/opencomputers/hosts/{id}/tunnels`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `target_port` | integer | Yes | Local port to expose | | `auth_mode` | string | No | `public` \| `tenant_only` \| `password` (default: `public`) | | `slug` | string | No | Custom slug for the public URL | ```json { "id": "tun_abc", "host_id": "host_abc123", "slug": "my-app-dev", "target_port": 3000, "auth_mode": "public", "public_url": "https://api.miosa.ai/t/my-app-dev", "enabled": true, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ``` ### Get / Update / Delete a tunnel **`GET /api/v1/opencomputers/hosts/{id}/tunnels/{tunnel_id}`** **`PATCH /api/v1/opencomputers/hosts/{id}/tunnels/{tunnel_id}`** | Field | Type | Description | |-------|------|-------------| | `target_port` | integer | Change target port | | `auth_mode` | string | Change auth mode | | `enabled` | boolean | Enable or disable the tunnel | **`DELETE /api/v1/opencomputers/hosts/{id}/tunnels/{tunnel_id}`** — `204 No Content` --- ## Agents Dispatch an AI agent to complete a task autonomously on the host. ### Dispatch an agent **`POST /api/v1/opencomputers/hosts/{id}/agent/dispatch`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `task` | string | Yes | Natural-language task description | | `model_id` | string | No | Override the default model | | `max_turns` | integer | No | Maximum conversation turns (default: 20) | | `context` | object | No | Additional context key-value pairs | ```json { "id": "sess_abc", "host_id": "host_abc123", "task": "Run the test suite and fix any failing tests", "status": "pending", "max_turns": 20, "turns_used": 0, "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z", "completed_at": null, "error": null } ``` **Session statuses:** `pending` | `running` | `completed` | `failed` | `cancelled` ### List / Get sessions **`GET /api/v1/opencomputers/hosts/{id}/agent/sessions`** **`GET /api/v1/opencomputers/hosts/{id}/agent/sessions/{session_id}`** ### Stream agent events (SSE) **`GET /api/v1/opencomputers/hosts/{id}/agent/sessions/{session_id}/stream`** ### Cancel a session **`DELETE /api/v1/opencomputers/hosts/{id}/agent/sessions/{session_id}`** — `204 No Content` --- ## Inference Clusters Group multiple hosts to serve an LLM over an OpenAI-compatible endpoint. ### List clusters **`GET /api/v1/opencomputers/clusters`** ### Create a cluster **`POST /api/v1/opencomputers/clusters`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Cluster name | | `model` | string | Yes | Model to serve (e.g. `llama3:70b`) | | `host_ids` | string[] | Yes | IDs of hosts in the cluster | ```json { "id": "cl_abc", "name": "my-cluster", "model": "llama3:70b", "slug": "my-cluster", "status": "active", "host_ids": ["host_abc123"], "inference_url": "https://api.miosa.ai/inference/my-cluster/v1", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ``` The `inference_url` is OpenAI-compatible: ``` POST {inference_url}/chat/completions ``` ### Get / Start / Stop / Delete **`GET /api/v1/opencomputers/clusters/{id}`** **`POST /api/v1/opencomputers/clusters/{id}/start`** **`POST /api/v1/opencomputers/clusters/{id}/stop`** **`DELETE /api/v1/opencomputers/clusters/{id}`** — `204 No Content` --- ## Secrets Store encrypted key-value secrets accessible to the host agent at runtime. ### Tenant-scoped secrets | Endpoint | Method | Description | |----------|--------|-------------| | `/opencomputers/secrets` | GET | List tenant secrets | | `/opencomputers/secrets` | POST | Create a secret | | `/opencomputers/secrets/{id}` | PATCH | Update value or description | | `/opencomputers/secrets/{id}` | DELETE | Delete a secret | ### Host-scoped secrets | Endpoint | Method | Description | |----------|--------|-------------| | `/opencomputers/hosts/{id}/secrets` | GET | List host secrets | | `/opencomputers/hosts/{id}/secrets` | POST | Create a host secret | | `/opencomputers/hosts/{id}/secrets/{secret_id}` | DELETE | Delete a host secret | **Create request:** ```json { "name": "GITHUB_TOKEN", "value": "ghp_xxx", "description": "CI token" } ``` **Response (value is never returned):** ```json { "id": "sec_abc", "name": "GITHUB_TOKEN", "description": "CI token", "host_id": null, "tenant_id": "t_abc", "created_at": "2026-01-01T00:00:00Z", "updated_at": "2026-01-01T00:00:00Z" } ``` --- ## SDK Examples ```typescript const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY }); // Register a host — save host_key immediately const host = await miosa.openComputers.hosts.create({ name: 'my-mac' }); console.log('Host key (save this!):', host.host_key); // Run a command const job = await miosa.openComputers.jobs.run(host.id, { command: 'npm test', }); console.log('Exit code:', job.exit_code); console.log('Output:', job.stdout); // Expose a local dev server const tunnel = await miosa.openComputers.tunnels.create(host.id, { target_port: 3000, }); console.log('Public URL:', tunnel.public_url); // Dispatch an AI agent const session = await miosa.openComputers.agents.dispatch(host.id, { task: 'Run the test suite and fix any failing tests', max_turns: 30, }); console.log('Session:', session.id, session.status); ``` ```python client = miosa.Miosa(api_key="msk_u_...") # Register a host host = client.open_computers.hosts.create( miosa.resources.open_computers.types.HostCreateParams(name="my-mac") ) print("Host key:", host.host_key) # Run a command from miosa.resources.open_computers.types import JobRunParams job = client.open_computers.jobs.run(host.id, JobRunParams(command="npm test")) print(f"Exit code: {job.exit_code}, stdout: {job.stdout}") # Create a tunnel from miosa.resources.open_computers.types import TunnelCreateParams tunnel = client.open_computers.tunnels.create(host.id, TunnelCreateParams(target_port=3000)) print("Public URL:", tunnel.public_url) ``` ```go client := miosa.NewClient(os.Getenv("MIOSA_API_KEY")) // Register a host host, err := client.OpenComputers.Hosts.Create(ctx, miosa.CreateHostInput{ Name: "my-mac", }) if err != nil { log.Fatal(err) } fmt.Println("Host key:", *host.HostKey) // Run a command job, err := client.OpenComputers.Jobs.Run(ctx, host.ID, miosa.RunJobInput{ Command: "npm test", }) if err != nil { log.Fatal(err) } fmt.Printf("Exit code: %d\nStdout: %s\n", *job.ExitCode, *job.Stdout) // Create a tunnel tunnel, err := client.OpenComputers.Tunnels.Create(ctx, host.ID, miosa.CreateTunnelInput{ TargetPort: 3000, }) fmt.Println("Public URL:", tunnel.PublicURL) ``` ```elixir client = Miosa.client("msk_u_...") # Register a host {:ok, host} = Miosa.OpenComputers.Hosts.create(client, %{name: "my-mac"}) IO.puts("Host key: #{host["host_key"]}") # Run a command {:ok, job} = Miosa.OpenComputers.Jobs.run(client, host["id"], %{command: "npm test"}) IO.puts("Exit code: #{job["exit_code"]}") # Create a tunnel {:ok, tunnel} = Miosa.OpenComputers.Tunnels.create(client, host["id"], %{target_port: 3000}) IO.puts("Public URL: #{tunnel["public_url"]}") ``` ```java MiosaClient miosa = new MiosaClient(System.getenv("MIOSA_API_KEY")); // Register a host HostData host = miosa.openComputers().hosts() .create(new CreateHostParams("my-mac")); System.out.println("Host key: " + host.hostKey); // Run a command JobData job = miosa.openComputers().jobs() .run(host.id, new RunJobParams("npm test")); System.out.printf("Exit code: %d%nStdout: %s%n", job.exitCode, job.stdout); // Create a tunnel TunnelData tunnel = miosa.openComputers().tunnels() .create(host.id, new CreateTunnelParams(3000)); System.out.println("Public URL: " + tunnel.publicUrl); ``` --- ## Common Errors | Status | Code | Cause | |--------|------|-------| | 404 | `NOT_FOUND` | Host or resource does not exist in this tenant | | 403 | `FORBIDDEN` | Authenticated but not authorized | | 400 | `INVALID_ID` | Path parameter is not a valid ID | | 409 | — | Host is offline; command cannot be dispatched | | 502 | — | Host agent is unreachable | --- ## See also - [Computers API](/docs/api-reference/computers/) — managed MIOSA VMs vs. BYOC hosts - [Exec API](/docs/api-reference/exec/) — exec on managed computers - [Regions](/docs/api-reference/regions/) — region slugs accepted on host creation - [Error Codes](/docs/api-reference/errors/) — complete error code reference --- # Projects API URL: https://miosa.ai/docs/api-reference/projects Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/projects Source: src/routes/docs/api-reference/projects/+page.md Description: API reference for creating and managing projects inside MIOSA workspaces. A **Project** is the app, website, lead magnet, document, workflow, or customer build inside a workspace. Projects own sandboxes, computers, deployments, databases, storage buckets, volumes, functions, jobs, auth configuration, integrations, and custom domains. Base path: `/api/v1/projects` If a resource create call includes `project_slug`, `project_name`, or `external_project_id`, MIOSA resolves or creates the project before creating the resource. You can also create projects explicitly with this API. ## Endpoints | Method | Path | Description | |---|---|---| | `GET` | `/projects` | List projects for the organization | | `POST` | `/projects` | Create a project inside a workspace | | `GET` | `/projects/{id}` | Get one project | | `PATCH` | `/projects/{id}` | Update a project | | `GET` | `/projects/{id}/preview-domain` | Read the project preview domain | | `PUT` | `/projects/{id}/preview-domain` | Set the project preview domain | | `DELETE` | `/projects/{id}/preview-domain` | Clear the project preview domain | | `GET` | `/projects/{id}/preview-domain/verify` | Check DNS readiness | | `GET` | `/workspaces/{id}/projects` | List projects in one workspace | --- ## List Projects **`GET /api/v1/projects`** ### Query Parameters | Parameter | Type | Description | |---|---|---| | `workspace_id` | UUID | Limit results to one workspace | ### Response - `200 OK` ```json { "data": [ { "id": "660e8400-e29b-41d4-a716-446655440001", "tenant_id": "tnt_abc123", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "external_workspace_id": "clinic_123", "external_project_id": "project_789", "name": "Lead Magnet", "slug": "lead-magnet", "description": null, "metadata": {}, "settings": {}, "created_at": "2026-05-18T10:00:00Z", "updated_at": "2026-05-18T10:00:00Z" } ], "total": 1 } ``` ```bash curl "https://api.miosa.ai/api/v1/projects?workspace_id=550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Create a Project **`POST /api/v1/projects`** A project must belong to a workspace. Supply one of `workspace_id`, `workspace_slug`, or `external_workspace_id`. ### Request Body | Field | Type | Required | Description | |---|---|---|---| | `workspace_id` | UUID | Conditional | Existing MIOSA workspace ID | | `workspace_slug` | string | Conditional | Existing workspace slug | | `external_workspace_id` | string | Conditional | Your external workspace/customer ID | | `name` | string | Yes | Human-readable project name | | `slug` | string | No | URL-safe identifier. Auto-derived from `name` if omitted. | | `external_project_id` | string | No | Your project/app/document ID | | `description` | string | No | Optional description | | `metadata` | object | No | Caller metadata stored on the project | ### Example ```bash curl -X POST https://api.miosa.ai/api/v1/projects \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workspace_slug": "dr-smith-clinic", "name": "Lead Magnet", "slug": "lead-magnet", "external_workspace_id": "clinic_123", "external_project_id": "project_789" }' ``` ### Response - `201 Created` Full project object. ### Errors | Status | Code | Cause | |---|---|---| | 400 | `WORKSPACE_REQUIRED` | No `workspace_id`, `workspace_slug`, or `external_workspace_id` was supplied | | 404 | `WORKSPACE_NOT_FOUND` | Workspace selector did not resolve inside the organization | | 422 | `VALIDATION_FAILED` | Name, slug, or external project ID failed validation | --- ## Get a Project **`GET /api/v1/projects/{id}`** Returns one project if it belongs to the authenticated organization. ```bash curl https://api.miosa.ai/api/v1/projects/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Update a Project **`PATCH /api/v1/projects/{id}`** ### Request Body | Field | Type | Description | |---|---|---| | `name` | string | New display name | | `slug` | string | New URL-safe slug, unique inside the workspace | | `description` | string | New description (`null` to clear) | | `external_project_id` | string | Your project/app/document ID | | `external_workspace_id` | string | Your customer/workspace ID | | `metadata` | object | Replacement metadata map | | `settings` | object | Replacement settings map. Prefer the preview-domain endpoint for domain changes. | ```bash curl -X PATCH https://api.miosa.ai/api/v1/projects/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Summer Lead Magnet"}' ``` --- ## Use Projects on Resource Create Most create endpoints accept the same ownership fields: ```json { "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "external_workspace_id": "clinic_123", "external_project_id": "project_789" } ``` You can use slugs instead of UUIDs: ```json { "workspace_slug": "dr-smith-clinic", "workspace_name": "Dr. Smith Clinic", "project_slug": "lead-magnet", "project_name": "Lead Magnet" } ``` This applies to sandboxes, computers, deployments, databases, storage buckets, volumes, edge functions, cron jobs, project auth, and integrations. Derived records inherit ownership automatically. --- ## Project Preview Domain **`PUT /api/v1/projects/{id}/preview-domain`** Sets a project-level base domain for generated URLs. This overrides the workspace preview domain and organization preview domain for resources in this project. ```bash curl -X PUT https://api.miosa.ai/api/v1/projects/{id}/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"preview_domain":"program.drsmithclinic.com"}' ``` Response: ```json { "scope": "project", "id": "660e8400-e29b-41d4-a716-446655440001", "preview_domain": "program.drsmithclinic.com", "effective_domain": "program.drsmithclinic.com", "status": "pending_dns", "dns_status": "pending", "url_examples": { "default_preview": "https://.program.drsmithclinic.com", "port_preview": "https://3000-.sandbox.program.drsmithclinic.com", "deployment": "https://.program.drsmithclinic.com" } } ``` Required DNS records: | Record type | Name | Value | |---|---|---| | `CNAME` | `*` | `proxy.miosa.ai` | | `CNAME` | `*.sandbox` | `proxy.miosa.ai` | Verify: ```bash curl https://api.miosa.ai/api/v1/projects/{id}/preview-domain/verify \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Clear and inherit from the workspace/organization fallback: ```bash curl -X DELETE https://api.miosa.ai/api/v1/projects/{id}/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ## See also - [Workspaces API](/docs/api-reference/workspaces/) - parent resource - [Ownership and Attribution](/docs/platform/attribution/) - how IDs flow to usage and events - [Sandboxes API](/docs/api-reference/sandboxes/) - create resources inside a project - [Deployments API](/docs/api-reference/deployments/) - publish projects to URLs --- # Regions API URL: https://miosa.ai/docs/api-reference/regions Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/regions Source: src/routes/docs/api-reference/regions/+page.md Description: List available regions and pin Computers and Sandboxes to a specific region. MIOSA Computers and Sandboxes run in geographic regions. Every Computer (and every Sandbox, since a Sandbox is a lightweight Computer flavor) is created in exactly one region and stays there for its lifetime. Base path: `/api/v1/regions` --- ## Overview - **3 regions are live today.** All have equal status — none are preview, none are restricted. - **Default region** when you don't specify one: `us-west-la`. - **All VM sizes** (XS, S, M, L, XL) are available in every region for both Computers and Sandboxes. - **Latency** is roughly equidistant for US users — pick by data residency or proximity preference rather than performance. - **No multi-region resources.** A Computer lives in exactly one region. To run workloads in multiple regions, create multiple Computers. --- ## Available Regions | Slug | Location | City | Status | |------------------|-------------------|------------------|--------| | `us-west-la` | US West | Los Angeles, CA | Live | | `us-east-ny` | US East | New York, NY | Live | | `us-mia` | US East 2 | Miami | Live | The region slug is what you pass on create and what comes back on read — every Computer and Sandbox response includes a `region` field, e.g. `"region": "us-west-la"`. --- ## Specify a region on create Pass `region` in the request body when creating a Computer or Sandbox. If omitted, the default (`us-west-la`) is used. ```python computer = client.computers.create( name="ny-build-box", size="M", region="us-east-ny", ) print(computer.region) # "us-east-ny" ``` ```typescript const computer = await client.computers.create({ name: 'ny-build-box', size: 'M', region: 'us-east-ny', }); console.log(computer.region); // "us-east-ny" ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "ny-build-box", "size": "M", "region": "us-east-ny" }' ``` The same `region` field works on `POST /api/v1/sandboxes`. ### Errors | Status | Error | Cause | |--------|----------------------|------------------------------------------------| | 400 | `invalid_region` | Slug not in the list above | | 409 | `region_unavailable` | Transient capacity issue — retry or pick another | --- ## Default region behavior If `region` is omitted from the create request: - Computers default to **`us-west-la`**. - Sandboxes default to **`us-west-la`**. - The response still includes the resolved `region` so you always know where the resource lives. You can change your tenant's default region in dashboard settings — that override applies to API calls that omit `region`. --- ## List regions **`GET /api/v1/regions`** Returns the regions available to your tenant. Useful for region pickers in UIs. ### Response — `200 OK` ```json { "regions": [ { "slug": "us-west-la", "name": "US West (Los Angeles)", "city": "Los Angeles", "country": "US", "status": "live", "default": true }, { "slug": "us-east-ny", "name": "US East (New York)", "city": "New York", "country": "US", "status": "live", "default": false }, { "slug": "us-mia", "name": "US East 2 (Miami)", "city": "Miami", "country": "US", "status": "live", "default": false } ] } ``` ### Response Fields | Field | Type | Description | |------------|---------|-------------------------------------------------------| | `slug` | string | Stable identifier — pass this as `region` on create | | `name` | string | Human-readable label (safe to render in UIs) | | `city` | string | City the region is hosted in | | `country` | string | Two-letter ISO country code | | `status` | string | Always `"live"` today — reserved for future states | | `default` | boolean | `true` for exactly one region (the tenant default) | ```bash curl https://api.miosa.ai/api/v1/regions \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` The list endpoint is unauthenticated-friendly — you can use a short-lived ticket or a public token to populate region pickers without exposing your `msk_*` key. --- ## FAQ ### Why these 3 regions? We picked Los Angeles, New York, and Miami to cover the US west coast, east coast, and central corridor. The three sites are roughly equidistant for the median US user, and they each sit on independent network and power footprints — so a regional outage at one doesn't take the others down. ### How do I pick a region? Pick by **data residency** first. If you have a customer or compliance requirement that pins data to a coast, that decides for you. If you have no residency requirement, pick by **proximity to your users or your CI runners** — shorter physical distance means lower RTT for VNC streaming and PTY round-trips. Compute performance itself is identical across regions. ### Can a Computer span multiple regions? No. A Computer (or Sandbox) lives in exactly one region for its lifetime. To run workloads in multiple regions: - Create one Computer per region. - Use OpenComputers federation if you want a single logical workspace across them (see [OpenComputers](/docs/api-reference/open-computers/)). There's no live migration between regions today. ### Will more regions ship? Yes — EU and APAC regions are on the roadmap. When they ship they'll appear in `GET /api/v1/regions` with the same `"status": "live"` shape. Watch the [changelog](/docs/changelog/) for announcements. --- ## See also - [Computers API](/docs/api-reference/computers/) — pass `region` on create - [Sandboxes API](/docs/api-reference/sandboxes/) — sandbox region support - [OpenComputers](/docs/api-reference/open-computers/) — federate BYOC hosts across regions --- # API Reference / Releases URL: https://miosa.ai/docs/api-reference/releases Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/releases Source: src/routes/docs/api-reference/releases/+page.md Description: The immutable build artifact — static tarball or dynamic rootfs / OCI image, sha256-keyed. First-class `deployment_releases` records and their dedicated endpoints arrive in Phase 2B of the deployment refactor. Today, release content is referenced via the `artifact_uri` / `artifact_sha256` fields on `deployment_versions`. This page documents the steady-state target. A **Release** is the physical artifact produced by a build — what a [Version](/docs/api-reference/versions/) references. See [Releases](/docs/deploy/releases/) for the conceptual model. ## Endpoints (Phase 2B) ```http GET /api/v1/releases/:id GET /api/v1/deployments/:id/versions/:version_id/release ``` Releases are not typically created directly — they're produced by [Publish](/docs/api-reference/deployments/#publish). The endpoints above are for inspection and audit. ## Release shape ```json { "id": "rel_...", "deployment_version_id": "ver_...", "service_id": "svc_...", "tenant_id": "...", "kind": "static", "storage_uri": "s3://miosa-releases/dep_xyz/rel_abc.tar.zst", "sha256": "a5e6f0c1...", "size_bytes": 184320, "start_command": null, "port": null, "metadata": { "build_log_uri": "s3://...", "build_duration_ms": 8420 }, "created_at": "2026-05-14T18:19:48Z" } ``` For dynamic releases: `kind: "oci" | "rootfs"`, `start_command` and `port` populated. ## Immutability Releases are content-addressed by sha256. A release with sha256 `abc...` is always the same bytes. This is what makes [Rollback](/docs/deploy/rollback/) trivial and horizontal scaling identical across instances. A release is never mutated. To "change" it, publish a new version, which produces a new release. ## Garbage collection Static release artifacts older than the retention window (default 30 days for archived versions) are eligible for GC. The version row remains for audit; the artifact itself may be removed from object storage. If you attempt to rollback to a version whose artifact has been GC'd, MIOSA returns `410 Gone`. Active versions (any deployment's `active_version_id`) are never GC'd. ## Permissions | Action | Scope | |---|---| | Get | `deployments:read` | ## See also - [Versions](/docs/api-reference/versions/) — parent resource - [Deployments](/docs/api-reference/deployments/) — top-level resource - [Releases](/docs/deploy/releases/) — conceptual model --- # Sandboxes API URL: https://miosa.ai/docs/api-reference/sandboxes Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandboxes Source: src/routes/docs/api-reference/sandboxes/+page.md Description: API reference for creating, managing, and executing code in MIOSA sandboxes — ephemeral Firecracker microVMs. Sandboxes are ephemeral Firecracker microVMs that restore from a pre-seeded snapshot, accept exec and file operations, expose previews, and are billed by compute/runtime usage. They are designed for AI-agent code execution workloads, artifact generation, and app previews. Base path: `/api/v1/sandboxes` All endpoints require `Authorization: Bearer <api_key>`. Get a key from the [MIOSA dashboard](https://app.miosa.ai/settings/api-keys). For higher-level access, use the official SDKs: [Python](/docs/sdks/python/), [TypeScript](/docs/sdks/typescript/), [Go](/docs/sdks/go/), [Java](/docs/sdks/java/), [Elixir](/docs/sdks/elixir/). --- ## List Sandbox Templates **`GET /api/v1/sandbox-templates`** Returns the platform-managed sandbox template catalog. Pass one of these IDs as `template_id` when creating a sandbox. The catalog describes what your app should run: workdir, install command, start command, preview port, readiness probe, and artifact paths. ```bash curl https://api.miosa.ai/api/v1/sandbox-templates \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ### Response — `200 OK` ```json { "default_template_id": "miosa-sandbox", "data": [ { "id": "nextjs", "name": "Next.js", "image_id": "miosa-sandbox", "category": "web", "cpu_count": 2, "memory_mb": 2048, "disk_mb": 4096, "workdir": "/workspace", "preview_port": 3000, "install_command": "npm install", "start_command": "npm run dev -- --hostname 0.0.0.0 --port 3000", "readiness_probe": { "type": "http", "url": "http://127.0.0.1:3000" } } ] } ``` Use `GET /api/v1/sandbox-templates/{id}` to fetch one template. Add `?include_aliases=true` on the list endpoint to include compatibility aliases such as `python-3.12`, `react-vite`, and `static`. --- ## Sandbox Template BuildSpec Phase 4 introduces the public BuildSpec contract for reusable sandbox templates. The API validates and normalizes BuildSpecs, persists tenant-owned template records, queues template build records, and runs a builder worker that turns the normalized BuildSpec into a Firecracker rootfs artifact. ### Get the BuildSpec schema **`GET /api/v1/sandbox-templates/build-spec`** ```bash curl https://api.miosa.ai/api/v1/sandbox-templates/build-spec \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ### Validate a BuildSpec **`POST /api/v1/sandbox-templates/validate`** ```bash curl -X POST https://api.miosa.ai/api/v1/sandbox-templates/validate \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "build_spec": { "from": "node:22-bookworm", "vcpu": 2, "memoryMib": 2048, "diskMib": 8192, "steps": [ {"run": "corepack enable"}, {"workdir": "/workspace"} ], "env": {"NODE_ENV": "development"}, "workdir": "/workspace", "user": "root", "startCmd": "pnpm dev --host 0.0.0.0 --port 3000", "readyCmd": "curl -f http://127.0.0.1:3000", "previewPort": 3000, "artifactPaths": ["/workspace"] } }' ``` Response: ```json { "valid": true, "build_spec": { "from": "node:22-bookworm", "vcpu": 2, "memoryMib": 2048, "diskMib": 8192, "steps": [{"run": "corepack enable"}, {"workdir": "/workspace"}], "env": {"NODE_ENV": "development"}, "workdir": "/workspace", "user": "root", "startCmd": "pnpm dev --host 0.0.0.0 --port 3000", "readyCmd": "curl -f http://127.0.0.1:3000", "previewPort": 3000, "artifactPaths": ["/workspace"] } } ``` Validation errors use stable codes: | Code | Meaning | |------|---------| | `REQUIRED` | A required field such as `from` is missing. | | `UNSUPPORTED_BASE_IMAGE` | Alpine/distroless bases are rejected for public agent templates. | | `OUT_OF_RANGE` | Resource values are outside tenant-safe defaults. | | `INVALID_INTEGER` | Numeric fields are not integers. | | `INVALID_PORT` | Preview port is outside `1..65535`. | | `INVALID_PATH` | Workdir or artifact path is not absolute. | | `INVALID_STEP` | A build step is not a supported object. | ### Create a custom template **`POST /api/v1/sandbox-templates`** ```bash curl -X POST https://api.miosa.ai/api/v1/sandbox-templates \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Agent Web App", "slug": "agent-web-app", "description": "Reusable Node app sandbox", "build_spec": { "from": "node:22-bookworm", "vcpu": 2, "memoryMib": 2048, "diskMib": 8192, "startCmd": "pnpm dev --host 0.0.0.0 --port 3000", "readyCmd": "curl -f http://127.0.0.1:3000", "previewPort": 3000, "artifactPaths": ["/workspace"] } }' ``` ### Queue a template build **`POST /api/v1/sandbox-templates/{template_id}/builds`** ```bash curl -X POST https://api.miosa.ai/api/v1/sandbox-templates/tpl_123/builds \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` Build records start in `queued`. The builder worker moves them through `building`, `snapshotting`, `certifying`, `distributing`, and then `ready` or `failed`. `ready` means the rootfs exists, a Firecracker memory snapshot has been seeded, certification passed, and the rootfs/snapshot artifacts have been published to the fleet artifact source. Each compute host reconciles or fetches the artifacts before it boots that custom template. After that, pass the template slug to `POST /api/v1/sandboxes` as `template_id`. If a custom template exists but is not ready, sandbox creation returns `409 TEMPLATE_NOT_READY`. If snapshot seeding fails, the build fails with `SNAPSHOT_FAILED`; if certification fails, it fails with `CERTIFICATION_FAILED`. ### Read template builds ```bash curl https://api.miosa.ai/api/v1/sandbox-templates/tpl_123/builds \ -H "Authorization: Bearer $MIOSA_API_KEY" curl https://api.miosa.ai/api/v1/sandbox-template-builds/build_123 \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ### Observe and control template builds ```bash curl https://api.miosa.ai/api/v1/sandbox-template-builds/build_123/logs \ -H "Authorization: Bearer $MIOSA_API_KEY" # Browsers/EventSource cannot send Authorization headers. Issue a short-lived # SSE ticket first, then pass it as ?ticket=... curl -X POST https://api.miosa.ai/api/v1/auth/sse-ticket \ -H "Authorization: Bearer $MIOSA_API_KEY" curl -N "https://api.miosa.ai/api/v1/sandbox-template-builds/build_123/logs/stream?ticket=$SSE_TICKET" \ -H "Accept: text/event-stream" curl -X POST https://api.miosa.ai/api/v1/sandbox-template-builds/build_123/cancel \ -H "Authorization: Bearer $MIOSA_API_KEY" curl -X POST https://api.miosa.ai/api/v1/sandbox-template-builds/build_123/retry \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` `/logs` returns persisted lifecycle events from build metadata. `/logs/stream` opens an SSE stream that first replays persisted events, then emits live `build_event` messages until the build reaches `ready`, `failed`, or `cancelled`. Example SSE payload: ```text event: build_event data: {"event":"snapshotting","state":"snapshotting","at":"2026-05-13T10:00:00Z"} ``` Retry is allowed for `failed` or `cancelled` builds. Cancel is accepted for non-terminal builds and records `BUILD_CANCELLED`. ### BuildSpec fields | Field | Type | Description | |-------|------|-------------| | `from` | string | OCI base image. Debian/Ubuntu/Bookworm bases are recommended. | | `vcpu` | integer | Template-owned vCPU count. | | `memoryMib` | integer | Template-owned memory in MiB. | | `diskMib` | integer | Template-owned root disk in MiB. | | `steps` | array | Ordered build steps. Each step may define `run`, `workdir`, or `env`. | | `env` | object | Runtime-default environment variables. | | `workdir` | string | Runtime working directory. Defaults to `/workspace`. | | `user` | string | Runtime user. Defaults to `root`. | | `startCmd` | string | Long-running command used by `/template/start`. | | `readyCmd` | string | Readiness command for the started app. | | `previewPort` | integer | Default preview port for `/expose` and template start. | | `artifactPaths` | array | Paths returned by the artifact manifest. | --- ## Create a Sandbox **`POST /api/v1/sandboxes`** Spawns a new sandbox VM. The API returns the sandbox record immediately after the create request is accepted; poll `GET /api/v1/sandboxes/{id}` or subscribe to events until `state` is `running` and `ready` is `true`. The current production alias, `miosa-sandbox`, points at `miosa-sandbox`. On the deployed six-host fleet, the snapshot path benchmarks at **48 ms p50 / 51 ms p95 runtime boot** and **156 ms p50 / 208 ms p95 create-to-ready**. ### Auth Bearer token required. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `template_id` | string | No | Boot template. Defaults to `miosa-sandbox`. See available templates below. | | `cpu_count` | integer | No | vCPU count. Default `1`, default cap `4`. | | `memory_mb` | integer | No | RAM in MB. Default `1024`, default cap `8192`. | | `disk_mb` | integer | No | Disk in MB. Default `3072`, default cap `10240`. | | `timeout_sec` | integer | No | Max wall-clock seconds before force-destroy. Day-long/always-on use is available when billing and policy allow it. | | `env` | object | No | Key-value env vars injected at boot. | | `metadata` | object | No | Arbitrary caller-supplied metadata stored on the record. | | `auto_start` | boolean | No | If `true`, MIOSA starts the selected template after the sandbox reaches `running`. Generated-app platforms usually keep this false, write files first, then call `/template/start`. | | `workspace_id` | UUID | No | Existing MIOSA workspace that owns the sandbox. Defaults to the organization default workspace. | | `workspace_slug` | string | No | Existing or auto-created workspace slug. | | `workspace_name` | string | No | Workspace display name if auto-created. | | `project_id` | UUID | No | Existing MIOSA project that owns the sandbox. Defaults to the workspace default project. | | `project_slug` | string | No | Existing or auto-created project slug inside the workspace. | | `project_name` | string | No | Project display name if auto-created. | | `external_workspace_id` | string | No | Your customer/account/workspace ID. | | `external_user_id` | string | No | Your end-user ID. | | `external_project_id` | string | No | Your project/app/document ID. | **Available templates:** | `template_id` | Description | |---------------|-------------| | `miosa-sandbox` | Stable production alias for the current sandbox image. | | `nextjs` | Next.js app preview profile. | | `vite-react` | Vite React app preview profile. | | `python` | Python script/artifact generation profile. | | `streamlit` | Streamlit data app preview profile. | | `gradio` | Gradio ML/demo app preview profile. | | `static-html` | Static HTML/CSS/JS preview profile. | ### Request Headers | Header | Description | |--------|-------------| | `Idempotency-Key` | Client-generated key (UUID recommended). Same key within 24 h returns the existing sandbox instead of creating a new one. | ### Response — `201 Created` ```json { "id": "sbx_01j9xr2t4fk8me3n5q", "tenant_id": "tnt_abc123", "owner_id": "usr_def456", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789", "template_id": "miosa-sandbox", "image_id": "miosa-sandbox", "state": "provisioning", "ready": false, "cpu_count": 1, "memory_mb": 1024, "disk_size_mb": 3072, "boot_path": null, "boot_ms": null, "preview_url": "https://sbx01j9x.sandbox.miosa.app", "timeout_sec": 300, "total_runtime_sec": null, "metadata": {}, "created_at": "2026-04-25T10:00:00Z", "started_at": null, "ready_at": null, "destroyed_at": null } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_TEMPLATE` | `template_id` is not a recognized template. | | 402 | `INSUFFICIENT_CREDITS` | Not enough credits to provision the VM. | | 409 | `SANDBOX_LIMIT_EXCEEDED` | Tenant has reached the concurrent sandbox limit (default 10). | | 422 | `VALIDATION_ERROR` | Invalid field values (for example, a resource request above the tenant's plan cap). | --- ## Start a Template App **`POST /api/v1/sandboxes/{id}/template/start`** After your platform writes generated files into `/workspace`, call this endpoint to run the selected template lifecycle. MIOSA runs the template install command, launches the start command in the background, stores PID/log paths, and returns the preview URL. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/template/start \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"install":true}' ``` Response: ```json { "data": { "status": "started", "template_id": "nextjs", "workdir": "/workspace", "preview_port": 3000, "preview_url": "https://abc12345.sandbox.miosa.app", "logs_path": "/tmp/miosa-run/template.log", "pid_path": "/tmp/miosa-run/template.pid", "artifact_paths": ["/workspace"] } } ``` You can override `install_command`, `start_command`, `port`, or `workdir` in the request body when your generated project needs a custom command. --- ## Get Artifacts **`GET /api/v1/sandboxes/{id}/artifacts`** Returns the template artifact contract and current lifecycle metadata. ```bash curl https://api.miosa.ai/api/v1/sandboxes/{id}/artifacts \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` The response includes `preview.url`, `preview.port`, artifact paths, and template lifecycle log/PID paths. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "template_id": "miosa-sandbox", "workspace_slug": "dr-smith-clinic", "workspace_name": "Dr. Smith Clinic", "project_slug": "lead-magnet", "project_name": "Lead Magnet", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "external_project_id": "project_789", "memory_mb": 1024, "env": {"MY_VAR": "hello"} }' ``` --- ## List Sandboxes **`GET /api/v1/sandboxes`** Returns all sandboxes belonging to the authenticated tenant. ### Query Parameters | Parameter | Type | Description | |---|---|---| | `workspace_id` | UUID | Filter to one MIOSA workspace | | `project_id` | UUID | Filter to one MIOSA project | | `external_workspace_id` | string | Filter by your customer/account ID | | `external_user_id` | string | Filter by your end-user ID | | `external_project_id` | string | Filter by your project/app/document ID | | `state` | string | Filter by lifecycle state: `provisioning`, `running`, `paused`, `destroyed`, `error`. | ### Auth Bearer token required. ### Response — `200 OK` ```json { "data": [ { "id": "sbx_01j9xr2t4fk8me3n5q", "template_id": "miosa-sandbox", "state": "running", "cpu_count": 1, "memory_mb": 1024, "created_at": "2026-04-25T10:00:00Z" } ] } ``` ```bash curl "https://api.miosa.ai/api/v1/sandboxes?state=running" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get a Sandbox **`GET /api/v1/sandboxes/{id}`** ### Auth Bearer token required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. | ### Response — `200 OK` Full sandbox object (same shape as the create response with current `state`). ### Errors | Status | Code | Cause | |--------|------|-------| | 404 | `NOT_FOUND` | Sandbox does not exist or belongs to a different tenant. | ```bash curl https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Destroy a Sandbox **`DELETE /api/v1/sandboxes/{id}`** Terminates the VM immediately and settles billing. ### Auth Bearer token required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. | ### Response — `200 OK` ```json { "id": "sbx_01j9xr2t4fk8me3n5q", "state": "destroyed", "total_runtime_sec": 42 } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 404 | `NOT_FOUND` | Sandbox does not exist or belongs to a different tenant. | ```bash curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Execute a Command **`POST /api/v1/sandboxes/{id}/exec`** Runs a shell command inside the sandbox. Blocks until the process exits. ### Auth Bearer token required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. Must be in `running` state. | ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `command` | string | Yes | Shell command to execute. | | `timeout` | integer | No | Per-command timeout in seconds. Default `30`, max `300`. | | `working_dir` | string | No | Working directory inside the VM. Default `/root`. | | `env` | object | No | Env vars for this invocation only. | ### Response — `200 OK` ```json { "data": { "sandbox_id": "sbx_01j9xr2t4fk8me3n5q", "stdout": "2\n", "stderr": "", "exit_code": 0 } } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 409 | `SANDBOX_NOT_RUNNING` | Sandbox is not in `running` state. | | 504 | — | Command timed out. | ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/exec \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "python3 -c \"print(1+1)\"", "timeout": 30}' ``` --- ## Stream Command Output **`POST /api/v1/sandboxes/{id}/exec/stream`** Runs a command and streams stdout/stderr events as Server-Sent Events. Use this for installer output, long-running agent tasks, and build logs that should show progress before the command exits. Request body is the same as `/exec`. ```bash curl -N -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/exec/stream \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Accept: text/event-stream" \ -H "Content-Type: application/json" \ -d '{"command":"npm install && npm run build","working_dir":"/workspace","timeout":300}' ``` Example events: ```text event: stdout data: {"line":"added 342 packages"} event: exit data: {"exit_code":0} ``` --- ## Open a Terminal Session **`POST /api/v1/sandboxes/{id}/terminal`** Creates a PTY session inside a running sandbox and returns a short-lived stream token for browser WebSocket clients. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `cols` | integer | No | Initial terminal width. Defaults to `80`. | | `rows` | integer | No | Initial terminal height. Defaults to `24`. | | `shell` | string | No | Shell command. Defaults to login bash when available. | ### Response — `201 Created` ```json { "session_id": "52d1df5a0a3a0018", "ws_url": "wss://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/terminal/stream", "stream_auth": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...", "stream_auth_expires_at": "2026-05-13T12:30:00Z" } ``` Open the WebSocket with: ```text wss://api.miosa.ai/api/v1/sandboxes/{id}/terminal/stream?session_id={session_id}&cols=120&rows=32&token={stream_auth} ``` Clients should send raw terminal input as binary frames. Resize events are sent as JSON text frames: ```json {"type":"resize","cols":120,"rows":32} ``` Delete the session when finished: ```bash curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/{id}/terminal/{session_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Expose a Preview Port **`POST /api/v1/sandboxes/{id}/expose`** Returns a public, tenant-aware preview URL for a server running inside the sandbox. Use this when an agent generated a Vite/Next/FastAPI/Flask app and started it on a local port. Inside the sandbox, bind dev servers to `0.0.0.0`, not `localhost`: ```bash npm run dev -- --host 0.0.0.0 --port 5173 python -m http.server 8000 --bind 0.0.0.0 ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `port` | integer | No | Port inside the sandbox to expose. If omitted, MIOSA uses the template lifecycle preview port when available. Must be `1` through `65535` when provided. | ### Response — `200 OK` ```json { "url": "https://5173-sbx01j9x.sandbox.miosa.app" } ``` If the tenant has a white-label preview domain configured, the same endpoint returns that domain instead of the platform default. Default app ports such as `3000`, `5173`, `8080`, `8000`, and `80` may be returned as `https://{slug}.sandbox.{domain}`; non-default ports use `https://{port}-{slug}.sandbox.{domain}`. ### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `MISSING_PARAM` | `port` was omitted and the sandbox template has no default preview port. | | 409 | `SANDBOX_NOT_RUNNING` | Sandbox is not in `running` state. | | 422 | `INVALID_PORT` | Port is outside `1` through `65535`. | ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/expose \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"port": 5173}' ``` For static artifacts such as PDFs, images, Markdown, CSV, or ZIP files, write them under `/workspace` and download them through the files API. For web apps, start a server and expose the port. --- ## Promote a Sandbox to a Deployment **`POST /api/v1/sandboxes/{id}/deploy`** Promotes a running sandbox into a persistent deployment URL. This is the publish step for generated-app platforms: the sandbox remains the runtime behind the deployment route, its auto-destroy timer is cancelled, and the deployment is marked `running` against the sandbox VM IP and preview port. Use this after your generated app is serving successfully through `/template/start` or `/expose`. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Human-readable deployment name. Used to create the deployment slug. | | `port` | integer | No | Runtime port to route. Defaults to the template lifecycle preview port, then `80`. | | `domain` | string | No | Custom domain to attach. | | `custom_domain` | string | No | Alias for `domain`, used by frontend clients. | ### Response — `201 Created` ```json { "deployment_id": "dep_01j9xr2t4fk8me3n5q", "url": "https://my-app-a1b2c3.acme.miosa.app", "state": "running" } ``` Managed deployment URLs are tenant-scoped: `https://{deployment-slug}.{tenant-slug}.miosa.app`. MIOSA persists the sandbox runtime target on the deployment and reconciles running routes, so a temporary proxy restart or admin API outage can be repaired from the database. ### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `MISSING_PARAM` | `name` was omitted. | | 409 | `SANDBOX_NOT_RUNNING` | Sandbox is not in `running` state. | | 409 | `SANDBOX_RUNTIME_UNAVAILABLE` | Sandbox is marked running but has no VM IP yet. | | 422 | `INVALID_PORT` | Port is outside `1` through `65535`. | | 422 | `VALIDATION_ERROR` | Deployment record validation failed. | ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/deploy \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"my-app","port":8000}' ``` --- ## Upload a File **`POST /api/v1/sandboxes/{id}/files`** Writes a file to the sandbox filesystem. Two request formats are accepted. ### Auth Bearer token required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. Must be in `running` state. | ### Request Body — JSON | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes | Absolute destination path inside the sandbox. | | `content` | string | Yes | Base64-encoded file content. | ```json { "path": "/workspace/script.py", "content": "cHJpbnQoJ2hlbGxvJyk=" } ``` ### Request Body — Multipart Alternatively, send `multipart/form-data` with fields: | Field | Description | |-------|-------------| | `path` | Absolute destination path. | | `file` | File part containing the raw content. | ### Response — `200 OK` ```json { "data": { "sandbox_id": "sbx_01j9xr2t4fk8me3n5q", "path": "/workspace/script.py", "size": 16 } } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 409 | `SANDBOX_NOT_RUNNING` | Sandbox is not in `running` state. | | 413 | — | File exceeds the 100 MB upload limit. | ```bash # JSON (base64-encoded content) curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/files \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"path\": \"/workspace/script.py\", \"content\": \"$(base64 -w0 script.py)\"}" # Multipart curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/files \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -F "path=/workspace/script.py" \ -F "file=@script.py" ``` --- ## Download a File **`GET /api/v1/sandboxes/{id}/files/{path}`** Downloads a file from the sandbox filesystem. ### Auth Bearer token required. ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. Must be in `running` state. | | `path` | string | URL-encoded path to the file inside the sandbox (leading `/` stripped). | ### Response — `200 OK` Raw file bytes with `Content-Type: application/octet-stream`. ### Errors | Status | Code | Cause | |--------|------|-------| | 404 | `FILE_NOT_FOUND` | Path does not exist inside the sandbox. | | 409 | `SANDBOX_NOT_RUNNING` | Sandbox is not in `running` state. | ```bash curl -o output.json \ "https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/files/workspace%2Foutput.json" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## List Files **`GET /api/v1/sandboxes/{id}/files?path=/workspace`** Lists files and directories in a sandbox directory. ```bash curl "https://api.miosa.ai/api/v1/sandboxes/{id}/files?path=/workspace" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Response: ```json { "data": { "path": "/workspace", "entries": [ {"name": "index.html", "path": "/workspace/index.html", "is_dir": false, "size": 128} ] } } ``` --- ## Stat a File **`POST /api/v1/sandboxes/{id}/files/stat`** Returns metadata for one file or directory. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/files/stat \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"path":"/workspace/index.html"}' ``` --- ## Read Logs **`GET /api/v1/sandboxes/{id}/logs`** Returns recent sandbox/template lifecycle logs. Pass `lines` to control the tail length. ```bash curl "https://api.miosa.ai/api/v1/sandboxes/{id}/logs?lines=200" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Stream logs with SSE: ```bash curl -N "https://api.miosa.ai/api/v1/sandboxes/{id}/logs/stream" \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Accept: text/event-stream" ``` --- ## Snapshot Lifecycle **`POST /api/v1/sandboxes/{id}/snapshots`** Creates a named checkpoint for a running sandbox. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/snapshots \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"comment":"before dependency upgrade"}' ``` List, inspect, restore, and delete snapshots: ```bash curl https://api.miosa.ai/api/v1/sandboxes/{id}/snapshots \ -H "Authorization: Bearer $MIOSA_API_KEY" curl https://api.miosa.ai/api/v1/sandboxes/{id}/snapshots/{snapshot_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/restore/{snapshot_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/{id}/snapshots/{snapshot_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Pause and Resume **`POST /api/v1/sandboxes/{id}/pause`** Pauses a running sandbox using the Firecracker snapshot path. Paused sandboxes keep their lifecycle record and can be resumed. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/pause \ -H "Authorization: Bearer $MIOSA_API_KEY" curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/resume \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Subscribe to Events (SSE) **`GET /api/v1/sandboxes/{id}/events`** Opens a Server-Sent Events (SSE) stream that emits sandbox lifecycle events. The connection closes automatically when the sandbox reaches `destroyed` or `error`. ### Auth Bearer token required (sent as `Authorization: Bearer <key>` header). ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | string | Sandbox ID. | ### Response — `200 OK` (event stream) `Content-Type: text/event-stream` **Event: `state_changed`** ``` event: state_changed data: {"state":"running","previous_state":"provisioning","timestamp":"2026-04-25T10:00:07Z"} ``` **Event: `exec_output`** ``` event: exec_output data: {"exec_id":"exec_abc","stream":"stdout","data":"2\n","timestamp":"2026-04-25T10:00:10Z"} ``` **Event: `error`** ``` event: error data: {"message":"VM terminated unexpectedly","timestamp":"2026-04-25T10:05:00Z"} ``` ```bash curl -N "https://api.miosa.ai/api/v1/sandboxes/sbx_01j9xr2t4fk8me3n5q/events" \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Accept: text/event-stream" ``` --- ## Sandbox Object The full sandbox object returned by create, get, and list endpoints: | Field | Type | Description | |-------|------|-------------| | `id` | string | Unique sandbox identifier. | | `tenant_id` | string | Owning tenant ID. | | `owner_id` | string | Creating user ID. | | `template_id` | string | Boot template name. | | `state` | string | Current lifecycle state. | | `cpu_count` | integer | Allocated vCPUs. | | `memory_mb` | integer | Allocated RAM in MB. | | `disk_size_mb` | integer | Allocated root disk in MB. | | `image_id` | string | Resolved rootfs image, for example `miosa-sandbox`. | | `boot_path` | string/null | Boot mechanism used, for example `snapshot` or `cold`. | | `boot_ms` | integer/null | Runtime boot time in milliseconds once known. | | `ready` | boolean | Whether envd and readiness checks are accepting work. | | `ready_at` | string/null | ISO timestamp when `ready` became true. | | `preview_url` | string/null | Tenant-aware base preview URL for the sandbox slug. Use `/expose` for a specific port. | | `timeout_sec` | integer | Max seconds before force-destroy. | | `total_runtime_sec` | integer \| null | Billed seconds. Set only after destruction. | | `metadata` | object | Caller-supplied metadata. | | `created_at` | string | ISO-8601 creation timestamp. | | `started_at` | string \| null | ISO-8601 timestamp when VM entered `running`. | | `destroyed_at` | string \| null | ISO-8601 timestamp when VM was destroyed. | --- ## Lifecycle States ``` provisioning → running → destroyed running → paused → running * → error (terminal) ``` | State | Description | |-------|-------------| | `provisioning` | VM is booting. Exec and file operations are not yet available. | | `running` | VM is reachable. All operations allowed. | | `paused` | VM has a RAM snapshot on disk. Can be resumed. | | `destroyed` | Terminal. VM is gone, billing is settled. | | `error` | Terminal failure. Destroy and create a new sandbox. | --- ## Error Response Format All error responses use this shape: ```json { "error": { "code": "NOT_FOUND", "message": "sandbox not found", "details": null } } ``` The `x-request-id` response header is set on every request. Include it in support requests. --- ## Rate Limits | Limit | Value | |-------|-------| | Requests per minute per workspace | 300 | | Concurrent sandboxes per tenant | 10 (contact support to increase) | | Max sandbox runtime | Plan/policy dependent; day-long and always-on sandboxes are supported when billing policy allows it | | Default resource shape | 1 vCPU, 1 GB RAM, 3 GB disk | | Default resource caps | 4 vCPU, 8 GB RAM, 10 GB disk | | Max exec timeout | 300 s (5 minutes) | | Max file upload size | 100 MB | Rate-limited responses return HTTP `429` with a `Retry-After` header indicating seconds to wait. --- ## See also - [Python SDK](/docs/sdks/python/) — `miosa` - [TypeScript SDK](/docs/sdks/typescript/) — `@miosa/sdk` - [Go SDK](/docs/sdks/go/) — `github.com/miosa-ai/miosa-go` - [Java SDK](/docs/sdks/java/) — `ai.miosa:miosa-sdk` - [Elixir SDK](/docs/sdks/elixir/) — `:miosa` - [Events (SSE)](/docs/api-reference/events/) — SSE reference for other MIOSA resources - [Error Codes](/docs/api-reference/errors/) — full error code catalog including sandbox-specific codes --- # Services API URL: https://miosa.ai/docs/api-reference/services Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/services Source: src/routes/docs/api-reference/services/+page.md Description: API reference for managing long-running systemd services inside MIOSA computers. Services are persistent processes managed by systemd inside a computer. Use them for web servers, background workers, databases, or any daemon that should survive across exec calls and restart automatically on failure. Base path: `/api/v1/computers/{id}/services` Services map directly to systemd unit files (`/etc/systemd/system/miosa-{name}.service`) managed by the in-VM envd daemon. The computer must be **running** to interact with services. --- ## Quick Start ```typescript const client = new Miosa(); // Register and start a web server const svc = await client.services.create(computerId, { name: 'webserver', command: 'python3 -m http.server 8080', cwd: '/home/user/app', restart: 'always', }); console.log(svc.status); // "starting" // Check status later const updated = await client.services.get(computerId, svc.id); console.log(updated.status); // "running" ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/services \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "webserver", "command": "python3 -m http.server 8080", "cwd": "/home/user/app", "restart": "always" }' ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/computers/{id}/services` | Create and start a service | | `GET` | `/computers/{id}/services` | List all services on a computer | | `GET` | `/computers/{id}/services/{name}` | Get a service by name | | `POST` | `/computers/{id}/services/{name}/start` | Start a stopped service | | `POST` | `/computers/{id}/services/{name}/stop` | Stop a running service | | `POST` | `/computers/{id}/services/{name}/restart` | Restart a service | | `DELETE` | `/computers/{id}/services/{name}` | Remove a service | --- ## Create a Service **`POST /api/v1/computers/{id}/services`** Writes the systemd unit file and starts the service. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Service name. Pattern: `[a-z][a-z0-9-]{0,62}` | | `command` | string | Yes | Full command to run | | `env` | object | No | Environment variables as `{ "KEY": "value" }` | | `cwd` | string | No | Working directory (default: `/home/user`) | | `restart` | string | No | `"always"`, `"on-failure"` (default), or `"no"` | ### Response — `201 Created` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "computer_id": "...", "tenant_id": "...", "name": "webserver", "command": "python3 -m http.server 8080", "env": {}, "cwd": "/home/user/app", "restart": "always", "status": "starting", "pid": null, "exit_code": null, "last_started_at": null, "last_exited_at": null, "created_at": "2026-04-11T00:00:00Z", "updated_at": "2026-04-11T00:00:00Z" } } ``` ### Status Values | Status | Description | |--------|-------------| | `stopped` | Registered but not started | | `starting` | Start dispatched to systemd | | `running` | systemd reports `ActiveState=active` | | `failed` | systemd reports `ActiveState=failed` | | `removed` | Unit file deleted; record kept for audit | ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `a service with this name already exists` | Name must be unique per computer | | 422 | Validation error | Invalid name format or restart policy | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/services \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "api-server", "command": "/home/user/app/server", "env": {"PORT": "3000", "NODE_ENV": "production"}, "cwd": "/home/user/app", "restart": "on-failure" }' ``` --- ## List Services **`GET /api/v1/computers/{id}/services`** ### Response — `200 OK` ```json { "data": [ { "id": "...", "name": "webserver", "status": "running", "pid": 12345, "restart": "always", "created_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/services \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get a Service **`GET /api/v1/computers/{id}/services/{name}`** ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Computer ID | | `name` | string | Service name | ### Response — `200 OK` Full service object (same as create response). ### Errors | Status | Error | Cause | |--------|-------|-------| | 404 | `service not found` | Does not exist on this computer | --- ## Start, Stop, Restart **`POST /api/v1/computers/{id}/services/{name}/start`** **`POST /api/v1/computers/{id}/services/{name}/stop`** **`POST /api/v1/computers/{id}/services/{name}/restart`** All return a `200 OK` with the updated service object. ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `service is already running` | Cannot start a running service | | 409 | `service is not running` | Cannot stop a non-running service | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash # Stop a service curl -X POST https://api.miosa.ai/api/v1/computers/{id}/services/webserver/stop \ -H "Authorization: Bearer $MIOSA_API_KEY" # Restart it curl -X POST https://api.miosa.ai/api/v1/computers/{id}/services/webserver/restart \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Remove a Service **`DELETE /api/v1/computers/{id}/services/{name}`** Stops the service and removes the systemd unit file. The database record transitions to `"removed"` status. ### Response — `200 OK` ```json { "data": { "name": "webserver", "status": "removed" } } ``` ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/services/webserver \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Common Recipes ### Deploy a Node.js app as a service ```typescript // Upload app files first await client.files.write(computerId, { path: '/home/user/app/server.js', content: serverJsContent, }); // Register and start const svc = await client.services.create(computerId, { name: 'node-app', command: 'node /home/user/app/server.js', env: { PORT: '3000' }, restart: 'on-failure', }); ``` ### Wait until a service is running ```python svc = client.services.create(computer_id, name="worker", command="/usr/bin/worker") while svc.status == "starting": time.sleep(1) svc = client.services.get(computer_id, svc.name) if svc.status != "running": raise RuntimeError(f"Service failed to start: {svc.status}") ``` ### Check for failed services in a fleet ```typescript const computers = await client.computers.list(); for (const computer of computers.data) { if (computer.status !== 'running') continue; const services = await client.services.list(computer.id); const failed = services.data.filter(s => s.status === 'failed'); if (failed.length > 0) { console.log(`${computer.name}: ${failed.length} failed services`); } } ``` --- # Snapshots API URL: https://miosa.ai/docs/api-reference/snapshots Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/snapshots Source: src/routes/docs/api-reference/snapshots/+page.md Description: API reference for creating, listing, and restoring Firecracker VM snapshots (checkpoints). Snapshots capture the full in-memory state of a running computer — CPU state, RAM, and filesystem — and store it durably. Restore a snapshot to spin up an identical computer in seconds. Base path: `/api/v1/computers/{id}/snapshots` Snapshots require the computer to be **running**. Creating a snapshot does not stop the computer. --- ## Quick Start ```typescript const client = new Miosa(); // Create a snapshot const snap = await client.snapshots.create(computerId, { comment: 'before-deploy-v2', }); // Poll until ready let status = snap.status; while (status !== 'ready') { await new Promise(r => setTimeout(r, 3000)); const updated = await client.snapshots.get(computerId, snap.id); status = updated.status; } // Restore — creates a new computer from the snapshot const restored = await client.snapshots.restore(computerId, snap.id); console.log(`Restored computer: ${restored.id}`); ``` ```bash # Create a snapshot curl -X POST https://api.miosa.ai/api/v1/computers/{id}/snapshots \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"comment": "before-deploy-v2"}' ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/computers/{id}/snapshots` | Create a snapshot | | `GET` | `/computers/{id}/snapshots` | List snapshots for a computer | | `GET` | `/computers/{id}/snapshots/{snap_id}` | Get a snapshot | | `DELETE` | `/computers/{id}/snapshots/{snap_id}` | Delete a snapshot | | `POST` | `/computers/{id}/restore/{snap_id}` | Restore from snapshot | | `GET` | `/computers/{id}/snapshots/{snap_id}/events` | SSE progress stream | --- ## Create a Snapshot **`POST /api/v1/computers/{id}/snapshots`** Initiates an asynchronous snapshot. The returned status will be `"creating"`. Poll `GET /snapshots/{snap_id}` or subscribe to SSE events for progress. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `comment` | string | No | Human-readable label (max 500 chars) | ### Response — `201 Created` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "computer_id": "...", "tenant_id": "...", "comment": "before-deploy-v2", "status": "creating", "state_size_bytes": null, "memory_size_bytes": null, "rootfs_size_bytes": null, "compressed_size_bytes": null, "s3_bucket": null, "s3_prefix": null, "error": null, "created_at": "2026-04-11T00:00:00Z", "updated_at": "2026-04-11T00:00:00Z" } } ``` ### Status State Machine ``` creating → uploading → ready any → failed ready → deleted ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 404 | `computer not found` | Computer does not exist or wrong tenant | | 409 | `computer is not running` | Snapshot requires running VM | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/snapshots \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"comment": "checkpoint-before-risky-op"}' ``` --- ## List Snapshots **`GET /api/v1/computers/{id}/snapshots`** ### Response — `200 OK` ```json { "data": [ { "id": "...", "comment": "before-deploy-v2", "status": "ready", "compressed_size_bytes": 524288000, "created_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/computers/{id}/snapshots \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get a Snapshot **`GET /api/v1/computers/{id}/snapshots/{snap_id}`** ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Computer ID | | `snap_id` | UUID | Snapshot ID | ### Response — `200 OK` Full snapshot object (same shape as create response). ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Snapshot belongs to a different tenant | | 404 | `snapshot not found` | Does not exist | ```bash curl https://api.miosa.ai/api/v1/computers/{id}/snapshots/{snap_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Delete a Snapshot **`DELETE /api/v1/computers/{id}/snapshots/{snap_id}`** Soft-deletes the snapshot (transitions to `"deleted"` status). S3 objects are cleaned up asynchronously. ### Response — `200 OK` ```json { "data": { "id": "...", "status": "deleted" } } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 404 | `snapshot not found` | Does not exist | | 409 | `snapshot is not in a deletable state` | Snapshot is being restored | ```bash curl -X DELETE https://api.miosa.ai/api/v1/computers/{id}/snapshots/{snap_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Restore from Snapshot **`POST /api/v1/computers/{id}/restore/{snap_id}`** Creates a new computer with the state from the snapshot. The original computer is unchanged. ### Response — `201 Created` ```json { "data": { "id": "new-computer-uuid", "name": "my-computer-restored", "status": "provisioning", "size": "small" }, "snapshot": { "id": "snap-uuid", "status": "restoring" } } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 404 | `snapshot not found` | Does not exist | | 409 | `snapshot is not ready` | Snapshot must be in `ready` state | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/restore/{snap_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## SSE Progress Stream **`GET /api/v1/computers/{id}/snapshots/{snap_id}/events`** Subscribe to real-time snapshot progress. Requires a short-lived ticket rather than a Bearer token (browsers cannot set Authorization on EventSource connections). ### Event Types | Event | Description | |-------|-------------| | `snapshot_creating` | Firecracker writing memory and VM state to disk | | `snapshot_uploading` | Compressing and uploading to object storage | | `snapshot_ready` | Upload complete; snapshot is usable | | `snapshot_failed` | Unrecoverable error — see `error` field | --- ## Common Recipes ### Automated checkpoint before risky operations ```python from miosa import Miosa client = Miosa() snap = client.snapshots.create(computer_id, comment="pre-migration") while snap.status not in ("ready", "failed"): time.sleep(2) snap = client.snapshots.get(computer_id, snap.id) if snap.status == "failed": raise RuntimeError(f"Snapshot failed: {snap.error}") # Proceed with risky operation knowing you can restore do_migration(computer_id) ``` ### Snapshot-based parallelism (clone a baseline) ```typescript // Build a baseline environment once, clone it N times for parallel jobs const baseline = await client.snapshots.create(setupComputerId, { comment: 'baseline-env', }); // Each restore creates an independent computer from the same state const workers = await Promise.all( Array.from({ length: 5 }, () => client.snapshots.restore(setupComputerId, baseline.id) ) ); ``` --- ## See also - [Computers API](/docs/api-reference/computers/) — computer lifecycle and clone endpoint - [Events (SSE)](/docs/api-reference/events/) — stream snapshot progress events - [Error Codes](/docs/api-reference/errors/) — snapshot-specific error codes --- # Streaming Exec API URL: https://miosa.ai/docs/api-reference/streaming-exec Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/streaming-exec Source: src/routes/docs/api-reference/streaming-exec/+page.md Description: API reference for streaming command output in real-time from MIOSA computers via SSE. Streaming exec runs a command on a computer and delivers output line-by-line via Server-Sent Events. Use it for long-running builds, installs, or any command where you want incremental output rather than waiting for completion. Base path: `/api/v1/computers/{id}/exec/stream` Standard `POST /computers/{id}/exec` waits for the command to finish before returning. Streaming exec is better for commands that run longer than a few seconds or that you want to monitor interactively. --- ## Quick Start ```typescript const client = new Miosa(); // Stream a long-running build for await (const event of client.exec.stream(computerId, { command: 'pip install torch', timeout: 300, })) { if (event.type === 'output') { process.stdout.write(event.data.line); } else if (event.type === 'exit') { console.log(`Exit code: ${event.data.exitCode}`); break; } } ``` ```bash # Obtain a streaming ticket first, then connect TICKET=$(curl -s -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/stream \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "pip install torch", "timeout": 300}' | jq -r .ticket) curl -N "https://api.miosa.ai/api/v1/computers/{id}/exec/stream/events?ticket=$TICKET" \ -H "Accept: text/event-stream" ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/computers/{id}/exec/stream` | Start a streaming exec and obtain a ticket | | `GET` | `/computers/{id}/exec/stream/events` | SSE stream (requires ticket) | | `POST` | `/computers/{id}/exec` | Non-streaming exec (waits for completion) | | `POST` | `/computers/{id}/exec/python` | Non-streaming Python exec | --- ## Start Streaming Exec **`POST /api/v1/computers/{id}/exec/stream`** Starts the command and returns a short-lived ticket for the SSE stream. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `command` | string | Yes | Shell command to execute | | `timeout` | integer | No | Timeout in seconds (default: 30, max: 300) | | `shell` | string | No | Shell to use (default: `/bin/bash`) | ### Response — `202 Accepted` ```json { "ticket": "exec_short_lived_token", "expires_at": 1712700060, "stream_url": "/api/v1/computers/{id}/exec/stream/events?ticket=exec_short_lived_token" } ``` The ticket expires in 60 seconds — open the EventSource connection immediately. ### Errors | Status | Error | Cause | |--------|-------|-------| | 409 | `COMPUTER_NOT_RUNNING` | Computer is not running | | 502 | `AGENT_UNAVAILABLE` | In-VM agent unreachable | ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/stream \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"command": "npm install", "timeout": 120}' ``` --- ## Connect to SSE Stream **`GET /api/v1/computers/{id}/exec/stream/events?ticket={ticket}`** ### Event Types | Event | Payload Fields | Description | |-------|---------------|-------------| | `output` | `line`, `stream` | A line of stdout or stderr | | `exit` | `exit_code`, `duration_ms` | Command finished; stream closes after this | | `timeout` | `timeout_seconds` | Command killed after timeout | | `error` | `message` | Internal error before command started | ### Event Payload Examples ``` event: output data: {"line": "Collecting torch\n", "stream": "stdout"} event: output data: {"line": " Downloading torch-2.3.0-...\n", "stream": "stdout"} event: exit data: {"exit_code": 0, "duration_ms": 45312} ``` ### `stream` Values | Value | Description | |-------|-------------| | `stdout` | Standard output | | `stderr` | Standard error | ```javascript // Browser const es = new EventSource(`/api/v1/computers/${id}/exec/stream/events?ticket=${ticket}`); es.addEventListener('output', e => { const { line, stream } = JSON.parse(e.data); console.log(`[${stream}] ${line}`); }); es.addEventListener('exit', e => { const { exit_code } = JSON.parse(e.data); console.log(`Exited with code ${exit_code}`); es.close(); }); es.addEventListener('error', () => { // Connection dropped or ticket expired es.close(); }); ``` --- ## Comparison: Streaming vs Non-Streaming | Aspect | `POST /exec` | `POST /exec/stream` | |--------|-------------|---------------------| | Response timing | After command completes | Immediate (ticket) | | Output delivery | All at once | Line by line via SSE | | Max timeout | 300 s | 300 s | | Best for | Quick commands | Long builds, installs | | SDK method | `exec.run()` | `exec.stream()` | --- ## Common Recipes ### Monitor a build ```python for event in client.exec.stream(computer_id, command="make build", timeout=300): if event.type == "output": print(event.data["line"], end="") elif event.type == "exit": if event.data["exit_code"] != 0: raise RuntimeError(f"Build failed with exit code {event.data['exit_code']}") print("Build succeeded") break ``` ### Capture both streams separately ```typescript const stdout: string[] = []; const stderr: string[] = []; for await (const event of client.exec.stream(computerId, { command: 'myapp 2>&1 | tee /tmp/output.log' })) { if (event.type === 'output') { if (event.data.stream === 'stdout') stdout.push(event.data.line); else stderr.push(event.data.line); } else if (event.type === 'exit') { break; } } ``` ### Timeout handling ```python try: for event in client.exec.stream(computer_id, command="long-task", timeout=60): if event.type == "timeout": print(f"Command killed after {event.data['timeout_seconds']}s") break elif event.type == "exit": break except Exception as e: print(f"Stream error: {e}") ``` --- # API Reference / Versions URL: https://miosa.ai/docs/api-reference/versions Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/versions Source: src/routes/docs/api-reference/versions/+page.md Description: Immutable deployment version sub-resource. List, get, promote, archive. A **Deployment Version** is the immutable record of one publish. See [Versions](/docs/deploy/versions/) for the conceptual model. ## Endpoints ```http GET /api/v1/deployments/:id/versions GET /api/v1/deployments/:id/versions/:version_id POST /api/v1/deployments/:id/versions/:version_id/promote ``` ## List ```http GET /api/v1/deployments/:id/versions GET /api/v1/deployments/:id/versions?state=ready GET /api/v1/deployments/:id/versions?workspace_id=550e8400-e29b-41d4-a716-446655440000 GET /api/v1/deployments/:id/versions?external_workspace_id=clinic_123 GET /api/v1/deployments/:id/versions?limit=50&cursor=... ``` Response: ```json { "data": [ { "id": "ver_...", "deployment_id": "dep_...", "version_number": 17, "kind": "static", "state": "ready", "artifact_sha256": "a5e6f0c1...", "source_sha256": "f1a3b8c2...", "promoted_at": "2026-05-14T18:20:00Z", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "external_workspace_id": "clinic_123", "external_user_id": "dr-smith-456", "created_at": "2026-05-14T18:19:48Z" } ], "next_cursor": "..." } ``` ## Get ```http GET /api/v1/deployments/:id/versions/:version_id ``` Returns the full version row including `artifact_manifest`, `runtime_image`, `runtime_command`, `runtime_port`, `health_check_path`, `build_log_uri`. ## Promote Make a specific ready version the active one for the deployment. ```http POST /api/v1/deployments/:id/versions/:version_id/promote ``` Body: empty, or optionally `{ "environment": "production" }`. Scopes: `deployments:write`. Requires `Idempotency-Key` for safety on retries. Promotion is different from publish: - **Publish** creates a new version from a sandbox source. - **Promote** points an existing ready version as active in an environment. Use promote for canary / staged rollouts: publish to a non-production environment first, validate, then promote to production. ## States | State | Meaning | |---|---| | `created` | Row exists, build not started | | `building` | Build in progress | | `ready` | Build succeeded, artifact uploaded, promotable | | `failed` | Build or health check failed | | `archived` | Promoted-out-of; still bootable for rollback | State transitions are one-way. Once `ready` or `failed`, a version stays that way. ## Permissions | Action | Scope | |---|---| | List, Get | `deployments:read` | | Promote | `deployments:write` | ## See also - [Deployments](/docs/api-reference/deployments/) — parent resource - [Releases](/docs/api-reference/releases/) — artifact reference - [Versions](/docs/deploy/versions/) — conceptual model - [Rollback](/docs/deploy/rollback/) — uses promote internally --- # Workspaces API URL: https://miosa.ai/docs/api-reference/workspaces Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/workspaces Source: src/routes/docs/api-reference/workspaces/+page.md Description: API reference for creating and managing MIOSA workspaces - the customer/client layer inside an organization. Workspaces group resources inside an organization. In a white-label platform, a workspace usually represents one downstream customer or client, such as `Dr. Smith Clinic` inside the `ClinicIQ` organization. Projects live inside workspaces, and sandboxes, computers, deployments, databases, storage buckets, volumes, functions, jobs, and domains belong to projects. Base path: `/api/v1/workspaces` A **default** workspace is created automatically when you sign up. Resources are assigned to the default workspace and its default project unless you specify workspace/project ownership at creation time. --- ## Quick Start ```typescript const client = new Miosa(); // reads MIOSA_API_KEY // Create a workspace for a downstream customer const ws = await client.workspaces.create({ name: 'Dr. Smith Clinic', slug: 'dr-smith-clinic', externalWorkspaceId: 'clinic_123', }); // Create a computer inside a project in that workspace const computer = await client.computers.create({ name: 'clinic-builder', templateType: 'miosa-desktop', workspaceId: ws.id, projectSlug: 'lead-magnet', projectName: 'Lead Magnet', }); // List computers in the workspace const { data } = await client.workspaces.listComputers(ws.id); ``` ```bash # curl equivalent curl -X POST https://api.miosa.ai/api/v1/workspaces \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Dr. Smith Clinic", "slug": "dr-smith-clinic", "external_workspace_id": "clinic_123"}' ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/workspaces` | List workspaces for the tenant | | `POST` | `/workspaces` | Create a workspace | | `GET` | `/workspaces/{id}` | Get a workspace | | `PATCH` | `/workspaces/{id}` | Update a workspace | | `PUT` | `/workspaces/{id}/settings` | Update workspace-level resource limits | | `GET` | `/workspaces/{id}/preview-domain` | Read the workspace preview domain | | `PUT` | `/workspaces/{id}/preview-domain` | Set the workspace preview domain | | `DELETE` | `/workspaces/{id}/preview-domain` | Clear the workspace preview domain | | `GET` | `/workspaces/{id}/preview-domain/verify` | Check DNS readiness | | `DELETE` | `/workspaces/{id}` | Delete a workspace | | `GET` | `/workspaces/{id}/computers` | List computers in a workspace | | `GET` | `/workspaces/{id}/stats` | Computer counts and resource totals | | `GET` | `/workspaces/{id}/usage` | Time-series credit and compute usage | | `GET` | `/workspaces/{id}/projects` | List projects in a workspace | --- ## List Workspaces **`GET /api/v1/workspaces`** Returns all workspaces belonging to the authenticated tenant. ### Response — `200 OK` ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "tenant_id": "...", "name": "default", "slug": "default", "external_workspace_id": null, "description": null, "metadata": {}, "is_default": true, "computer_count": 3, "created_at": "2026-04-11T00:00:00Z", "updated_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Create a Workspace **`POST /api/v1/workspaces`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Human-readable name (1–120 chars) | | `slug` | string | No | URL-safe identifier. Auto-derived from `name` if omitted. Pattern: `[a-z0-9][a-z0-9-]{0,79}` | | `external_workspace_id` | string | No | Your customer/account/workspace ID. Globally unique inside this MIOSA organization. | | `description` | string | No | Optional description | | `metadata` | object | No | Caller metadata stored on the workspace. | ### Response — `201 Created` Full workspace object (same shape as list items above). ### Errors | Status | Error | Cause | |--------|-------|-------| | 422 | `has already been taken` | Slug already exists in tenant | | 422 | `must start with alphanumeric and contain only a-z 0-9 -` | Invalid slug format | ```bash curl -X POST https://api.miosa.ai/api/v1/workspaces \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Dr. Smith Clinic", "slug": "dr-smith-clinic", "external_workspace_id": "clinic_123", "description": "Client workspace for Dr. Smith Clinic" }' ``` --- ## Get a Workspace **`GET /api/v1/workspaces/{id}`** ### Path Parameters | Parameter | Type | Description | |-----------|------|-------------| | `id` | UUID | Workspace ID | ### Response — `200 OK` Full workspace object. ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Workspace belongs to a different tenant | | 404 | `workspace not found` | Does not exist | ```bash curl https://api.miosa.ai/api/v1/workspaces/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Update a Workspace **`PATCH /api/v1/workspaces/{id}`** Update mutable workspace fields. Prefer treating the slug as stable once resources and URLs exist. ### Request Body | Field | Type | Description | |-------|------|-------------| | `name` | string | New display name | | `slug` | string | New URL-safe slug. Must remain unique inside the organization. | | `description` | string | New description (`null` to clear) | | `external_workspace_id` | string | Your customer/account/workspace ID | | `metadata` | object | Replacement metadata map | ### Response — `200 OK` Updated workspace object. ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Wrong tenant | | 404 | `workspace not found` | Does not exist | ```bash curl -X PATCH https://api.miosa.ai/api/v1/workspaces/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "production-us-east"}' ``` --- ## Delete a Workspace **`DELETE /api/v1/workspaces/{id}`** Deletes an empty workspace. Fails if the workspace contains computers or projects. ### Response — `200 OK` ```json { "deleted": true } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Wrong tenant | | 404 | `workspace not found` | Does not exist | | 409 | `workspace has computers` | Remove or move resources first | ```bash curl -X DELETE https://api.miosa.ai/api/v1/workspaces/{id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Update Workspace Settings **`PUT /api/v1/workspaces/{id}/settings`** Applies resource limits and default preferences for the workspace. These limits cap what any computer inside the workspace can do. Tenant-level limits still apply as a ceiling. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `max_computers` | integer | No | Maximum number of computers allowed in this workspace. `null` = no workspace-level cap (tenant limit applies). | | `max_computer_size` | string | No | Maximum `size` value a computer in this workspace may be created with: `"small"`, `"medium"`, or `"large"`. | | `default_computer_size` | string | No | Default `size` applied when creating a computer without an explicit `size`. | | `default_auto_stop_seconds` | integer | No | Auto-stop idle timeout (seconds) applied to new computers by default. `0` = no auto-stop. | | `allowed_template_types` | string[] | No | Allowlist of template names that can be used in this workspace. Empty array = all templates allowed. | ### Response — `200 OK` ```json { "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "settings": { "max_computers": 10, "max_computer_size": "large", "default_computer_size": "small", "default_auto_stop_seconds": 3600, "allowed_template_types": [] }, "updated_at": "2026-05-17T10:00:00Z" } ``` --- ## Workspace Preview Domain **`PUT /api/v1/workspaces/{id}/preview-domain`** Sets a client/workspace-level base domain for generated URLs. This overrides the organization preview domain for resources in this workspace unless a project has its own preview domain. ```bash curl -X PUT https://api.miosa.ai/api/v1/workspaces/{id}/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"preview_domain":"drsmithclinic.com"}' ``` Response: ```json { "scope": "workspace", "id": "550e8400-e29b-41d4-a716-446655440000", "preview_domain": "drsmithclinic.com", "effective_domain": "drsmithclinic.com", "status": "pending_dns", "dns_status": "pending", "url_examples": { "default_preview": "https://.drsmithclinic.com", "port_preview": "https://3000-.sandbox.drsmithclinic.com", "deployment": "https://.drsmithclinic.com" } } ``` Required DNS records: | Record type | Name | Value | |---|---|---| | `CNAME` | `*` | `proxy.miosa.ai` | | `CNAME` | `*.sandbox` | `proxy.miosa.ai` | Verify: ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/preview-domain/verify \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Clear and inherit from the organization/domain fallback: ```bash curl -X DELETE https://api.miosa.ai/api/v1/workspaces/{id}/preview-domain \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Wrong tenant | | 404 | `workspace not found` | Does not exist | | 422 | `VALIDATION_FAILED` | Unknown size value or invalid limit | ```bash curl -X PUT https://api.miosa.ai/api/v1/workspaces/{id}/settings \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "max_computers": 10, "max_computer_size": "large", "default_auto_stop_seconds": 3600 }' ``` --- ## List Computers in a Workspace **`GET /api/v1/workspaces/{id}/computers`** ### Response — `200 OK` ```json { "data": [ { "id": "...", "name": "runner-1", "status": "running", "size": "small", "template_type": "miosa-desktop", "created_at": "2026-04-11T00:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/computers \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## List Projects in a Workspace **`GET /api/v1/workspaces/{id}/projects`** Returns the projects owned by a workspace. Use this for client dashboards before listing the sandboxes, computers, deployments, and databases inside a project. ### Response - `200 OK` ```json { "data": [ { "id": "660e8400-e29b-41d4-a716-446655440001", "tenant_id": "...", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "external_workspace_id": "clinic_123", "external_project_id": "project_789", "name": "Lead Magnet", "slug": "lead-magnet", "description": null, "metadata": {}, "created_at": "2026-05-18T10:00:00Z", "updated_at": "2026-05-18T10:00:00Z" } ], "total": 1 } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/projects \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Workspace Stats **`GET /api/v1/workspaces/{id}/stats`** Returns a snapshot of computer counts grouped by status and the aggregate resource footprint for the workspace. ### Response — `200 OK` ```json { "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "computers": { "total": 5, "by_status": { "running": 3, "stopped": 2, "provisioning": 0, "error": 0 } }, "resources": { "total_vcpus": 7, "total_memory_mb": 28672, "total_disk_gb": 160 }, "settings": { "max_computers": 10, "max_computer_size": "large", "default_auto_stop_seconds": 3600 } } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/stats \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Get Workspace Usage **`GET /api/v1/workspaces/{id}/usage`** Returns time-series credit consumption and compute-hour totals for the workspace over a given window. Use this for per-workspace cost attribution dashboards. ### Query Parameters | Parameter | Type | Description | |-----------|------|-------------| | `window` | string | `"24h"` (default), `"7d"`, `"30d"` | | `granularity` | string | `"hour"` (default), `"day"` — bucket size for the time-series data | ### Response — `200 OK` ```json { "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "window": "7d", "granularity": "day", "total_credits_used": 1284, "total_compute_hours": 42.5, "series": [ { "ts": "2026-05-10T00:00:00Z", "credits_used": 198, "compute_hours": 6.6 }, { "ts": "2026-05-11T00:00:00Z", "credits_used": 211, "compute_hours": 7.0 } ] } ``` ### Errors | Status | Error | Cause | |--------|-------|-------| | 403 | `forbidden` | Wrong tenant | | 404 | `workspace not found` | Does not exist | | 422 | `VALIDATION_FAILED` | Unknown `window` or `granularity` value | ```bash curl "https://api.miosa.ai/api/v1/workspaces/{id}/usage?window=7d&granularity=day" \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` --- ## Common Recipes ### Move all computers to a new workspace Computers do not yet support a `move` operation on workspace assignment via `PATCH /computers/{id}`. Use the `POST /computers/{id}/move` endpoint instead: ```bash curl -X POST https://api.miosa.ai/api/v1/computers/{computer_id}/move \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"workspace_id": "target-workspace-id"}' ``` ### Enumerate all workspaces and their sizes ```typescript const { data: workspaces } = await client.workspaces.list(); for (const ws of workspaces) { console.log(`${ws.name}: ${ws.computerCount} computers`); } ``` ### Default workspace ID lookup If you need the default workspace ID programmatically: ```typescript const { data } = await client.workspaces.list(); const defaultWs = data.find(ws => ws.isDefault); console.log(defaultWs.id); ``` --- ## Common Errors | Status | Code | Cause | |--------|------|-------| | 403 | `forbidden` | Workspace belongs to a different tenant | | 404 | `workspace not found` | Does not exist | | 409 | `workspace has computers` | Cannot delete a workspace that contains computers | | 422 | `VALIDATION_FAILED` | Name or slug failed schema validation | --- ## See also - [Computers API](/docs/api-reference/computers/) — computers are scoped to workspaces - [Projects API](/docs/api-reference/projects/) — projects live inside workspaces - [Error Codes](/docs/api-reference/errors/) — `VALIDATION_FAILED` and general errors --- # Authentication URL: https://miosa.ai/docs/authentication Fallback URL: https://miosa.roberto-c49.workers.dev/docs/authentication Source: src/routes/docs/authentication/+page.md Description: How to authenticate with the MIOSA API using msk_ API keys or JWT tokens. # Authentication Every MIOSA API request carries a single `Authorization: Bearer <token>` header. The token is either an **API key** (prefix `msk_`) or a **JWT** from `POST /auth/login`. One key covers the entire platform — computers, desktop, files, exec, CUA agent, OSA, credits, and (with the right role) admin. No separate keys for AI, no per-product configuration. ## Credential hierarchy Diagram: graph TB MSK[msk_live_* workspace key] -->|server-side only| API[MIOSA API calls] MSK -->|mints via POST /previews/:id/share| BT[Browser token - scoped + short-lived] BT -->|browser only| EP[Specific sandbox endpoint] MSK -->|mints via POST /auth/sse-ticket| ST[SSE ticket - single use] ST -->|EventSource| SS[SSE stream] ## API Keys ### Key format ``` msk__ ``` | Prefix | Role | Capabilities | |--------|----------|---------------------------------------------------| | `msk_u_...` | user | Compute, desktop, files, CUA, OSA, credits | | `msk_a_...` | admin | Everything above **+** `/api/v1/admin/*` | | `msk_p_...` | platform | Tenant-wide automation issued by the MIOSA platform | Role is **orthogonal** to `purpose`, which picks the backend the key can call: - `purpose: api` — compute, desktop, files, CUA, OSA, credits - `purpose: optimal` — AI/LLM proxy (`/v1/chat/completions`, `/v1/responses`) If you need both, create two keys. They share the `msk_` family so SDK configuration is identical. ### Creating an API key ```bash curl -X POST https://api.miosa.ai/api/v1/api-keys \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Production SDK Key", "key_type": "user", "purpose": "api" }' ``` ```json { "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "key": "msk_u_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6", "key_prefix": "msk_u_a1b2c3", "name": "Production SDK Key", "key_type": "user", "key_purpose": "api", "rate_limit_rpm": 60, "status": "active", "created_at": "2026-04-17T00:00:00Z" } } ``` The raw key is **only returned once** at creation. Store it securely. If lost, revoke it and create a new one — the original cannot be recovered. User-role callers can only mint `user` keys. Admin and platform callers may mint any `key_type`. To issue admin keys programmatically, use the admin variant at `POST /admin/api-keys` (operator-internal — not part of the public docs). ### Using an API key Pass the key in the `Authorization` header: ```bash curl https://api.miosa.ai/api/v1/computers \ -H "Authorization: Bearer msk_u_a1b2c3d4e5f6..." ``` All five SDKs read `MIOSA_API_KEY` from the environment by default: ```python from miosa import Miosa # Explicit key client = Miosa(api_key="msk_u_...") # Or from environment: export MIOSA_API_KEY="msk_u_..." client = Miosa() ``` ```ts // Explicit key const miosa = new Miosa({ apiKey: 'msk_u_...' }); ``` ```go // Explicit key client := miosa.NewClient("msk_u_...") // Or from environment client := miosa.NewClient(os.Getenv("MIOSA_API_KEY")) ``` ```elixir # Explicit key client = Miosa.client("msk_u_...") # Or from environment client = Miosa.client(System.fetch_env!("MIOSA_API_KEY")) ``` ```java MiosaClient miosa = new MiosaClient("msk_u_..."); ``` ### Managing keys | Action | Endpoint | |--------|----------| | List your keys | `GET /api/v1/api-keys` | | Create a key | `POST /api/v1/api-keys` | | Revoke a key | `DELETE /api/v1/api-keys/{id}` | Revoking a key flips its status to `revoked`. The change is cached and enforced within seconds — subsequent requests return `401 Unauthorized`. Revocation is irreversible; create a new key if you need access restored. ### Admin keys `msk_a_*` and `msk_p_*` keys unlock everything under `/api/v1/admin/*` — user management, tenant operations, credit grants, fleet-wide computer control, Optimal model switching. These are gated behind the same `RequireAdmin` plug as admin JWTs. User-role callers hitting an admin endpoint receive `403 Forbidden`. Admin endpoints are operator-internal and not documented publicly. ## JWT Tokens JWT tokens are used by the MIOSA web UI and SSE ticket flows. They are issued on login and carry user identity + tenant context. ### Obtaining a token ```bash curl -X POST https://api.miosa.ai/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email": "user@example.com", "password": "your_password"}' ``` ```json { "token": "eyJhbGciOiJIUzUxMiIs...", "refresh_token": "eyJhbGciOiJIUzUxMiIs...", "user": { "id": "...", "email": "user@example.com" } } ``` ### Token lifecycle | Token | Lifetime | Refresh | |-------|----------|---------| | Access token | ~1 hour | Use refresh token | | Refresh token | 7 days | Re-login required | ```bash curl -X POST https://api.miosa.ai/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{"refresh_token": "eyJhbGciOiJIUzUxMiIs..."}' ``` For programmatic access, use **API keys**. JWTs are designed for browser sessions and expire frequently. ## SSE streams Server-Sent Event endpoints (CUA session events) cannot use a standard `Authorization` header because `EventSource` does not support custom headers. Instead, mint a one-time ticket first: ```bash # 1. Ask for a ticket (Bearer-authed — API key or JWT both work) curl -X POST \ https://api.miosa.ai/api/v1/computers/{id}/cua/sessions/{session_id}/sse-ticket \ -H "Authorization: Bearer msk_u_..." # {"ticket":"sset_abc123","expires_in":3600,"session_id":"..."} # 2. Open the stream with the ticket as a query param curl https://api.miosa.ai/api/v1/computers/{id}/cua/sessions/{session_id}/events?ticket=sset_abc123 ``` The ticket is single-use and expires after one hour. ## Rate limits | Scope | Limit | Window | |-------|-------|--------| | Developer API (`/api/v1/*`) | 300 requests | Per minute per key | | Admin API (`/api/v1/admin/*`) | 300 requests | Per minute per key | | Auth endpoints (`/auth/login`, `/auth/register`, `/auth/refresh`) | 60 requests | Per minute per IP | When rate limited, the API returns `429 Too Many Requests` with a `Retry-After` header. Rate-limit status is surfaced in every response: ``` X-RateLimit-Limit: 300 X-RateLimit-Remaining: 287 X-RateLimit-Reset: 1712700060 ``` ## Error codes | Status | Code | Meaning | |--------|------|---------| | 401 | `UNAUTHORIZED` | Missing / invalid / revoked credential | | 403 | `FORBIDDEN` | Valid credential but insufficient role | | 429 | `RATE_LIMITED` | Too many requests | ## Security best practices 1. **Never commit API keys** to version control. 2. **Use environment variables** or a secrets manager. 3. **Rotate keys** on exposure — revoke and create a new one. 4. **Scope by role** — hand out `msk_u_*` keys to most callers; reserve `msk_a_*` / `msk_p_*` for operational tooling. 5. **Separate keys for dev and prod** — easier to revoke blast radius. 6. **Restrict by IP** (admin keys) — pass `allowed_ips` when minting via `POST /admin/api-keys` to reject traffic from unexpected sources. --- # Changelog URL: https://miosa.ai/docs/changelog Fallback URL: https://miosa.roberto-c49.workers.dev/docs/changelog Source: src/routes/docs/changelog/+page.md Description: What's new in MIOSA — shipped features, SDK updates, and platform improvements. What's new in MIOSA. Updates ship continuously; this page is a human-readable digest grouped by week. The MIOSA SDKs will live in dedicated public repos for each language — `Miosa-osa/python-sdk`, `Miosa-osa/typescript-sdk`, etc. Mirrored from the monorepo via a CI subtree-split workflow. Direct installs remain unchanged. --- ## May 12 – 18, 2026 ### MCP server for AI coding assistants A native MCP server ships 22 computer-use tools — screenshot, click, type, scroll, drag, key chords, window management, clipboard, cursor, launch, and more — making any MCP-capable AI coding assistant a first-class driver for MIOSA Computers. See [Desktop Control →](/docs/computers/desktop/) ### Desktop double-click, scroll fix, and faster typing Fixed a scroll timing regression that caused scroll-up actions to stall. Added double-click as a distinct desktop action. Improved keystroke throughput for `type` commands. The Go SDK now ships a full `Computer` type with all 13 desktop actions. ### Python and TypeScript Sandbox SDKs reach full parity All Sandbox methods — create, exec, stream, snapshot, file I/O, suspend, resume — are now available in Python (sync + async) and TypeScript. The CLI routes sandbox commands through the native API rather than a secondary proxy. See [Sandbox SDK reference →](/docs/develop/sandboxes/) ### Fixed: SDK import errors and agent exports Resolved broken module imports introduced during SDK consolidation. `ExecResult` field names are now consistent across Python and TypeScript. Agent stub exports are correctly registered in the package index. --- ## May 5 – 11, 2026 ### Sandbox architecture: snapshot restore now live Sandbox snapshot restore is live at ~166ms p50. The platform captures snapshots automatically on suspend and restores them on resume — no cold boot penalty on warm sandboxes. See [Sandboxes →](/docs/develop/sandboxes/) ### Regions and computer embedding APIs New `GET /api/v1/regions` endpoint returns the current compute region list with capacity metadata. The embed API now returns a scoped, short-lived token for embedding a computer's desktop into your own UI — no iframe proxy required. See [Embedding →](/docs/computers/embedding/) ### Production deploy pipeline hardened The sandbox-to-deployment pipeline (source snapshot → builder → release → runtime instance) is fully wired in production. Build logs stream in real time. Static sites land on the edge; dynamic apps boot as runtime instances. See [Deploy overview →](/docs/deploy/overview/) ### Unified SDKs replace standalone sandbox package The standalone sandbox SDK package is deprecated. All sandbox functionality is available under the unified language SDKs (`miosa` for Python, `@miosa/sdk` for TypeScript, and equivalents in Go, Java, Elixir). Migration is a single import change; the API surface is identical. ### Removed - The separate sandbox CLI path is removed; all CLI sandbox commands route through the main API. - Legacy agent endpoints (`/computers/:id/cua/*`, `/computers/:id/agent/*`) now return `410 Gone`. Use desktop primitives directly or the MCP server. --- ## Apr 28 – May 4, 2026 ### CLI: public install via Homebrew The MIOSA CLI is now installable via Homebrew: ```sh brew install miosa-dev/tap/msk ``` The CLI supports `msk computers`, `msk sandboxes`, `msk deploy`, and `msk auth`. A device-flow OAuth login ties the CLI to your workspace in one browser step. See [CLI reference →](/docs/cli/) ### Account security pages New security section in account settings: active sessions, passkey management, two-factor setup, and connected OAuth providers. Sessions can be revoked individually. ### Admin: email campaign composer Platform operators can now compose and send targeted email campaigns to workspace segments directly from the admin console. ### Improved: AI engine token tracking Token usage from the AI engine is now tracked per-computer and surfaced in the usage API. Metered usage accumulates in real time against the workspace's billing period. See [Usage and Billing →](/docs/platform/usage-and-billing/) ### Improved: frontend workspace tabs and GPU pricing Workspace settings tabs now persist the active tab across navigation. GPU tier pricing is correctly reflected on the computer create form and the pricing page. ### Improved: admin observability and intelligence gateway Admin dashboard surfaces per-tenant active computer count, sandbox throughput, and deploy queue depth. The intelligence gateway proxy correctly handles streaming responses under load. --- ## Apr 21 – 27, 2026 ### Computer tab scrolling fixed Long-running computers with many open tabs (terminal, preview, files, etc.) now scroll the tab strip horizontally rather than overflowing off-screen. ### Computer create: services panel The computer create flow now includes a services panel for attaching data services (Postgres, Redis, object storage) at creation time. Environment variables are injected automatically. ### Environment variable persistence Environment variables set on a computer or sandbox are persisted across stop/start cycles. Variables set via API are visible in the in-computer terminal immediately. ### Warm pool admin controls Platform operators can configure warm pool size, pre-warm image targets, and drain schedules from the admin console. --- ## Apr 14 – 20, 2026 ### OpenComputers resources across all five SDKs Hosts, jobs, files, tunnels, and inference cluster resources are now available in all five SDKs (Python, TypeScript, Go, Elixir, Java). Async variants included in Python. See [SDK references →](/docs/sdks/python/) ### SDK surface expansion Ten new resource namespaces added: Sandboxes, Deployments, Versions, Releases, Runtime Instances, Domains, Data Services (Postgres, Redis, Storage), Network Policy, and Credits. Coverage tables are on each SDK page. ### envd: network tunnels The in-computer daemon now supports exposing internal services as tunnels — useful for previewing dev servers running inside a computer from outside the VM boundary. See [Previews →](/docs/develop/previews/) --- ## Mar 31 – Apr 13, 2026 ### Deployments API The Deployments API is in production: `POST /api/v1/sandboxes/:id/deploy` publishes a sandbox, `GET /api/v1/deployments` lists deployment history, and `POST /api/v1/deployments/:id/rollback` re-points the active version. All operations are available in the SDK and dashboard. See [Deploy →](/docs/deploy/overview/) ### Sandbox, Deploy, and Intelligence dashboards The dashboard ships three new top-level sections: Sandboxes (live usage, snapshot list), Deploy (deployment history, build logs, rollback), and Intelligence (AI engine usage, cost trends). ### API drift CI gate A new CI check compares the server's declared API surface against all five SDK clients on every pull request. Contract mismatches — missing endpoints, changed field names, type drift — fail the build before they reach production. ### Docs site launched The MIOSA docs site launched with the unified API key format (`msk_*`), Mermaid diagram rendering, six-language SDK tabs, and full-text search. --- ## Mar 2 – 29, 2026 ### Compute platform in production The compute engine and platform API are deployed to production. The provisioning flow is hardened with retry logic, crash guards, and correct error propagation. OAuth state mismatch now redirects to login rather than surfacing a raw error. ### Computer rename complete "Instance" is renamed to "Computer" across the API, dashboard, SDKs, and documentation. Existing API clients using `/instances/*` continue to work via redirect; new code should use `/computers/*`. See [Computers →](/docs/computers/overview/) ### Computer: `/enter` endpoint `POST /api/v1/computers/:id/enter` returns a scoped session token for direct desktop access. Used by the embed flow and the CLI `msk computers enter` command. ### 26 app templates with tier system The computer create flow ships 26 starting templates (landing page, SaaS scaffold, data dashboard, agent workspace, and more) organized by tier: free, pro, and enterprise. The template picker is a split-panel modal with live previews. ### Security hardening Sixteen security fixes: secret redaction from configure responses, XSS prevention on user-controlled fields, timer leak fixes, provisioning crash guard, and tenant slug collision prevention. Internal auth tokens are no longer included in API responses to unprivileged callers. ### Command Center The Command Center UI is live — manage AI agent sessions across computers, track cost by session, and view real-time agent status. Backend scheduling and cost accounting routes are wired. --- ## Feb 2 – Mar 1, 2026 ### Platform foundation shipped Stripe billing, audit logging, email notifications, promo codes, and OAuth providers are in production. Redis session persistence ensures computer state survives backend restarts. ### Go backend fully retired The last Go backend service is archived. The entire stack now runs on Elixir. Dead proxy code and legacy stubs are removed. ### Documentation architecture Internal technical documentation organized across all five service packages (703 files), covering architecture decisions, API contracts, deployment procedures, and runbooks. --- ## Jan 5 – Feb 1, 2026 ### Platform foundation Auth, billing groundwork, and initial security audit complete. 81 known issues triaged; 27 legacy backend issues closed. --- # CLI Reference URL: https://miosa.ai/docs/cli Fallback URL: https://miosa.roberto-c49.workers.dev/docs/cli Source: src/routes/docs/cli/+page.md Description: Complete reference for the MIOSA CLI — manage computers, sandboxes, deployments, data services, and AI agents from your terminal. The `miosa` CLI (`@miosa/cli`) is the command-line entry point for MIOSA. It manages computers, sandboxes, deployments, data services, and AI agents — all from your terminal. Commands marked **[planned]** are not yet shipped. All others are live in `@miosa/cli@0.2.0`. ## Install ```bash npm install -g @miosa/cli ``` ```bash pnpm add -g @miosa/cli ``` ```bash brew install miosa-ai/tap/miosa ``` ```bash curl -fsSL https://install.miosa.ai | sh ``` Installs to `~/.local/bin` (non-root) or `/usr/local/bin` (root). Override with `INSTALL_DIR=/usr/local/bin`. Verify the install: ```bash miosa --version ``` ## Configuration Config is stored at `~/.miosa/config.toml`: ```toml api_url = "https://api.miosa.ai/api/v1" api_key = "msk_..." ``` **Precedence:** CLI flag `--api-key` → `MIOSA_API_KEY` env var → config file → interactive prompt. | Environment variable | Description | |---|---| | `MIOSA_API_KEY` | API key (overrides config file) | | `MIOSA_BASE_URL` | Custom API endpoint | | `MIOSA_DEBUG=1` | Enable verbose debug output | --- ## Command Reference ### Getting Started #### `miosa login [--api-key key]` Authenticate with your MIOSA API key. Saves the key to `~/.miosa/config.toml`. Get an API key from [miosa.ai/settings/api-keys](https://miosa.ai/settings/api-keys). ```bash miosa login miosa login --api-key msk_yourkey echo "msk_yourkey" | miosa login # CI/non-TTY ``` #### `miosa logout` Remove the stored API key from `~/.miosa/config.toml`. #### `miosa whoami` Print the currently authenticated user, tenant, and plan. ```bash miosa whoami ``` #### `miosa status` Show auth state, endpoint, tenant, plan, credit balance, and active resource counts. ```bash miosa status ``` #### `miosa doctor` Run environment diagnostics — checks auth, API reachability, CLI version, and shell integration. ```bash miosa doctor ``` #### `miosa config [get|set|unset] [key] [value]` Read or write values in `~/.miosa/config.toml`. ```bash miosa config get api_url miosa config set api_url https://your-instance.ai/api/v1 miosa config unset api_url ``` #### `miosa up` Start the local development environment. Equivalent to running `miosa dev` with sensible defaults — watches for changes, forwards tunnels, and tails logs. ```bash miosa up miosa up --port 3000 ``` --- ### Computers #### `miosa computer create [--image img] [--name name] [--size size]` Provision a new cloud computer. ```bash miosa computer create miosa computer create --name dev-box --image ubuntu-22.04 --size medium miosa computer create --image debian-12 --size large --json ``` | Flag | Description | |---|---| | `--name <name>` | Human-readable name | | `--image <image>` | OS image slug (default: `ubuntu-22.04`) | | `--size <size>` | `small`, `medium`, `large` (default: `medium`) | | `--json` | Output as JSON | #### `miosa computer list [--json]` List all computers with their state, image, and last-seen time. ```bash miosa computer list miosa computer list --json | jq '.[].name' ``` #### `miosa computer get <name-or-id> [--json]` Show details for a specific computer including live telemetry. ```bash miosa computer get dev-box miosa computer get abc12345 --json ``` #### `miosa computer delete <name-or-id> [-f]` Delete a computer. Prompts for confirmation unless `-f`. ```bash miosa computer delete dev-box miosa computer delete abc12345 -f ``` #### `miosa computer start <name-or-id>` Start a stopped computer. ```bash miosa computer start dev-box ``` #### `miosa computer stop <name-or-id>` Stop a running computer. ```bash miosa computer stop dev-box ``` #### `miosa computer screenshot <name-or-id> [--out file]` Capture a screenshot of the computer's desktop and save it locally. ```bash miosa computer screenshot dev-box miosa computer screenshot dev-box --out screen.png ``` #### `miosa computer apps <name-or-id>` List running applications on the computer's desktop. ```bash miosa computer apps dev-box ``` #### `miosa computer open <name-or-id>` Open the computer's desktop stream in your browser. ```bash miosa computer open dev-box ``` #### `miosa computer exec <name-or-id> <cmd> [args...]` Run a command on the computer non-interactively. Streams stdout/stderr and exits with the remote exit code. ```bash miosa computer exec dev-box npm test miosa computer exec dev-box ls -- -la /tmp miosa computer exec dev-box make build --cwd /project --timeout 10m miosa computer exec dev-box server.js --env NODE_ENV=production --env PORT=8080 ``` | Flag | Description | |---|---| | `--cwd <dir>` | Working directory on the computer | | `--env KEY=VAL` | Environment variable (repeatable) | | `--timeout <duration>` | e.g. `30s`, `2m`, `1h` (default: `5m`) | #### `miosa computer files <name-or-id> <path>` List files at a path on the computer. ```bash miosa computer files dev-box /home/user miosa computer files dev-box /tmp -la ``` --- ### Interactive #### `miosa shell <name-or-id> [--cmd "..."]` Open an interactive PTY terminal on a computer. Exits with the remote shell's exit code. ```bash miosa shell dev-box miosa shell dev-box --cmd "htop" ``` #### `miosa watch <name-or-id>` Stream live telemetry and events from a computer. ```bash miosa watch dev-box ``` #### `miosa agent start <name-or-id> "<task>"` Dispatch an AI agent task on a computer. Streams thoughts, tool calls, and results in real time. ```bash miosa agent start dev-box "run the test suite and fix any failures" miosa agent start dev-box "update all npm dependencies" --steps 20 miosa agent start dev-box "profile the API and find bottlenecks" --timeout 600000 ``` | Flag | Description | |---|---| | `--model <model>` | Model override (e.g. `qwen3:32b`) | | `--steps <n>` | Max agent steps (default: `10`) | | `--timeout <ms>` | Timeout in milliseconds (default: `300000`) | | `--tools <list>` | Comma-separated tool list (default: `exec,fs`) | #### `miosa agent ls <name-or-id>` List all agent sessions on a computer. ```bash miosa agent ls dev-box miosa agent ls dev-box --json ``` #### `miosa agent get <name-or-id> <session-id>` Show details and current state for an agent session. ```bash miosa agent get dev-box sess_abc123 ``` #### `miosa agent task <name-or-id> <session-id> "<task>"` Send an additional task to a running agent session. ```bash miosa agent task dev-box sess_abc123 "also update the README" ``` #### `miosa agent stop <name-or-id> <session-id>` Stop a running agent session. ```bash miosa agent stop dev-box sess_abc123 ``` #### `miosa agent history <name-or-id> <session-id>` Print the full message history for an agent session. ```bash miosa agent history dev-box sess_abc123 miosa agent history dev-box sess_abc123 --json ``` --- ### Sandboxes #### `miosa sandbox create [--image img] [--name name]` Create a new sandbox. ```bash miosa sandbox create miosa sandbox create --name my-sandbox --image debian-12-sandbox-v8 ``` | Flag | Description | |---|---| | `--name <name>` | Human-readable name | | `--image <image>` | Sandbox image slug (default: `debian-12-sandbox-v8`) | | `--json` | Output as JSON | #### `miosa sandbox list [--json]` List all sandboxes with their state and image. ```bash miosa sandbox list miosa sandbox list --json | jq '.[].id' ``` #### `miosa sandbox exec <id> <cmd> [args...]` Execute a command inside a sandbox. Streams stdout/stderr and returns the exit code. ```bash miosa sandbox exec sb_abc123 python main.py miosa sandbox exec sb_abc123 npm test --timeout 5m ``` #### `miosa sandbox run <id> "<code>"` Run a code snippet directly inside a sandbox. ```bash miosa sandbox run sb_abc123 "print('hello')" miosa sandbox run sb_abc123 "import sys; print(sys.version)" ``` #### `miosa sandbox write-file <id> <remote-path> <local-file>` Write a local file into the sandbox filesystem. ```bash miosa sandbox write-file sb_abc123 /app/main.py ./main.py miosa sandbox write-file sb_abc123 /app/config.json ./config.json ``` #### `miosa sandbox read-file <id> <remote-path>` Read a file from the sandbox filesystem and print it to stdout. ```bash miosa sandbox read-file sb_abc123 /app/output.txt miosa sandbox read-file sb_abc123 /var/log/app.log > local.log ``` #### `miosa sandbox upload <id> <local-path> <remote-path>` Upload a file or directory to the sandbox. ```bash miosa sandbox upload sb_abc123 ./dist /app/dist miosa sandbox upload sb_abc123 ./data.csv /tmp/data.csv ``` #### `miosa sandbox download <id> <remote-path> [local-path]` Download a file or directory from the sandbox. ```bash miosa sandbox download sb_abc123 /app/output.json ./output.json miosa sandbox download sb_abc123 /tmp/results ./results/ ``` #### `miosa sandbox logs <id> [--follow]` Stream logs from a sandbox. ```bash miosa sandbox logs sb_abc123 miosa sandbox logs sb_abc123 --follow ``` --- ### Deploy #### `miosa deploy [--name name] [--env ENV=VAL]` Deploy the current directory. Detects the framework automatically. ```bash miosa deploy miosa deploy --name my-app miosa deploy --env NODE_ENV=production --env PORT=8080 miosa deploy --region us-east ``` | Flag | Description | |---|---| | `--name <name>` | Deployment name (defaults to directory name) | | `--env KEY=VAL` | Environment variable (repeatable) | | `--region <region>` | Target region | | `--json` | Output as JSON | #### `miosa deployments list [--json]` List all deployments. ```bash miosa deployments list miosa deployments list --json | jq '.[].name' ``` #### `miosa deployments get <name-or-id>` Show details for a deployment. ```bash miosa deployments get my-app ``` #### `miosa deployments logs <name-or-id> [--follow]` Stream logs for a deployment. ```bash miosa deployments logs my-app miosa deployments logs my-app --follow ``` #### `miosa deployments restart <name-or-id>` Restart a deployment. ```bash miosa deployments restart my-app ``` #### `miosa services list` List all services attached to deployments. ```bash miosa services list ``` #### `miosa domains list` List all custom domains. ```bash miosa domains list ``` #### `miosa domains add <deployment> <domain>` Attach a custom domain to a deployment. ```bash miosa domains add my-app app.example.com ``` #### `miosa domains remove <deployment> <domain>` Remove a custom domain. ```bash miosa domains remove my-app app.example.com ``` --- ### Data #### `miosa databases list` List all managed databases. ```bash miosa databases list miosa databases list --json ``` #### `miosa databases create [--name name] [--engine engine]` Provision a managed database. ```bash miosa databases create --name app-db --engine postgres miosa databases create --name cache --engine redis ``` | Flag | Description | |---|---| | `--name <name>` | Database name | | `--engine <engine>` | `postgres` or `redis` | #### `miosa databases get <name-or-id>` Show connection details and status for a database. ```bash miosa databases get app-db ``` #### `miosa storage list` List all object storage buckets. ```bash miosa storage list ``` #### `miosa storage create <name>` Create a new storage bucket. ```bash miosa storage create my-bucket ``` #### `miosa volumes list` List all persistent volumes. ```bash miosa volumes list ``` #### `miosa volumes create <name> [--size size]` Create a persistent volume. ```bash miosa volumes create app-data --size 10gb ``` --- ### Platform #### `miosa functions list` List all serverless functions. ```bash miosa functions list ``` #### `miosa functions deploy <path>` Deploy a function from a local file or directory. ```bash miosa functions deploy ./functions/handler.ts ``` #### `miosa cron list` List all scheduled cron jobs. ```bash miosa cron list ``` #### `miosa cron create "<schedule>" "<cmd>"` Create a cron job using a standard cron expression. ```bash miosa cron create "0 * * * *" "miosa sandbox exec sb_abc123 python report.py" ``` #### `miosa webhooks list` List all registered webhooks. ```bash miosa webhooks list ``` #### `miosa webhooks create --url <url> --events <events>` Register a new webhook. ```bash miosa webhooks create --url https://example.com/hook --events computer.started,computer.stopped ``` #### `miosa checkpoints list <name-or-id>` List checkpoints (snapshots) for a computer. ```bash miosa checkpoints list dev-box ``` #### `miosa snapshot <name-or-id> [--name name]` Create a snapshot of a computer or sandbox. ```bash miosa snapshot dev-box miosa snapshot dev-box --name before-upgrade ``` #### `miosa network-policy list` List network policies. ```bash miosa network-policy list ``` #### `miosa network-policy create --name <name> --rule <rule>` Create a network policy rule. ```bash miosa network-policy create --name allow-http --rule "allow:80,443" ``` #### `miosa api-keys list` List all API keys for the current tenant. ```bash miosa api-keys list miosa api-keys list --json ``` #### `miosa api-keys create [--name name] [--scopes scopes]` Create a new API key. ```bash miosa api-keys create --name ci-key --scopes computers:read,sandboxes:write ``` #### `miosa api-keys revoke <id>` Revoke an API key. ```bash miosa api-keys revoke key_abc123 ``` --- ### Workflow #### `miosa dev [--port n]` Start a local development session with hot reload, tunnel forwarding, and log tailing. ```bash miosa dev miosa dev --port 3000 ``` #### `miosa run <script>` Run a script defined in `miosa.json` or `package.json`. ```bash miosa run build miosa run test ``` #### `miosa link [name-or-id]` Link the current directory to a MIOSA computer or deployment. Writes a `.miosa` config file. ```bash miosa link miosa link dev-box ``` #### `miosa pull <name-or-id> [path]` Pull files from a computer or deployment into the current directory. ```bash miosa pull dev-box /app/dist miosa pull my-deploy . ``` #### `miosa tunnel open <name-or-id> --port <n>` Expose a computer port publicly via a MIOSA tunnel. ```bash miosa tunnel open dev-box --port 3000 miosa tunnel open dev-box --port 8080 --name my-app --watch ``` | Flag | Description | |---|---| | `--port <n>` | Port to expose (required) | | `--name <slug>` | Optional tunnel name | | `--watch` | Stay open and stream live events | #### `miosa tunnel list <name-or-id>` List active tunnels on a computer. #### `miosa tunnel close <name-or-id> <slug>` Revoke a tunnel. #### `miosa mcp serve [--port n]` Start a local MCP (Model Context Protocol) server that exposes MIOSA resources to AI tools. ```bash miosa mcp serve miosa mcp serve --port 8765 ``` #### `miosa cp <src> <dst>` Copy files between local and remote. Use `host:/path` notation for remote paths. ```bash # Upload miosa cp ./app.tar.gz dev-box:/tmp/ miosa cp -r ./dist dev-box:/var/www/html/ # Download miosa cp dev-box:/var/log/app.log ./ miosa cp dev-box:/home/user/report.pdf ~/Downloads/ ``` **Flags:** `-r` / `--recursive` for directories. --- ## Exit Codes | Code | Meaning | |---|---| | `0` | Success | | `1` | User error (bad arguments, resource not found, etc.) | | `2` | Network error | | `3` | Authentication / authorization error | | `4` | Server error | --- ## Troubleshooting **"No API key configured"** — Run `miosa login`. **"Computer not found"** — Check `miosa computer list` for the correct name or ID. **"Insufficient credits"** — Top up at [miosa.ai/billing](https://miosa.ai/billing). **Network errors** — Check your connection. Enable debug output: ```bash MIOSA_DEBUG=1 miosa computer list ``` **Custom endpoint (self-hosted)**: ```bash MIOSA_BASE_URL=https://your-instance.ai miosa computer list ``` --- # BYOC / OpenComputers URL: https://miosa.ai/docs/computers/byoc Fallback URL: https://miosa.roberto-c49.workers.dev/docs/computers/byoc Source: src/routes/docs/computers/byoc/+page.md Description: Register your own hardware (Mac, Linux, Windows). One command, your machine becomes a MIOSA computer. Apache 2.0. **OpenComputers** is MIOSA's bring-your-own-compute path. Install the agent on your own hardware — laptops, desktops, mini-PCs, even Raspberry Pi for light workloads — and it registers with MIOSA over an outbound WebSocket. Your machine becomes a MIOSA computer that you (or your AI agents) can drive through the same API as cloud computers. Apache 2.0. Local-first scheduling — work runs on your hardware when there's capacity; cloud overflow when there isn't. ## Why BYOC - **Already own the hardware.** No reason to pay for cloud capacity for workloads you have machines for. - **Local LLMs, local models.** GPU on your machine, MIOSA orchestrates the work. - **Data residency.** Work runs on your hardware, not in our datacenter. - **Cloud overflow.** Scale beyond your local fleet when needed; MIOSA places overflow on its cloud. - **Same API.** Your agent code doesn't care if a job runs on `miosa-host-01` (cloud) or `dad-mac-mini` (your home network). ## Install the agent (one command) ```bash # On the host machine curl -fsSL https://miosa.ai/install | sh ``` The installer: 1. Detects your OS (macOS / Linux / Windows; WSL2 supported). 2. Downloads the OSA agent binary. 3. Prompts for a registration token (from your MIOSA dashboard). 4. Starts the agent as a system service (`launchd` / `systemd` / `Windows Service`). The agent maintains an outbound WebSocket to MIOSA — no inbound ports, no public IP. NAT-friendly. ## Three modes | Mode | What's exposed | Use case | |---|---|---| | **Direct** | The host machine itself is one Computer | Use your laptop as a computer for a single agent | | **VM dispatch** | The agent creates Firecracker microVMs on the host; each is a Computer | Run multiple parallel agents on one beefy machine | | **Slicing** | One physical machine partitioned into N independent desktop sessions | Multi-tenant workstation; pre-K12 / kiosk fleet | Most setups use VM dispatch — gives you process isolation, snapshots, and lifecycle the same way cloud computers work. ## Use a BYOC computer ```typescript // List your hosts const hosts = await miosa.openComputers.hosts.list() // Create a computer on a specific host const computer = await miosa.computers.create({ name: "local-agent", templateType: "miosa-desktop", hostId: hosts[0].id, // optional; scheduler picks if omitted }) // From here, identical API as a cloud computer await computer.desktop.screenshot() ``` The scheduler honors local-first placement by default — if you have available capacity on a BYOC host, jobs land there. Override via explicit `hostId` or set scheduling preferences per workspace. ## Tunnels When you need direct browser access to a port running inside the host machine (a local dev server, a vendor admin UI, your home Home Assistant), open a MIOSA tunnel: ```typescript const tunnel = await miosa.openComputers.tunnels.create(hostId, { port: 8123, name: "home-assistant", }) console.log(tunnel.public_url) // → https://home-assistant.your-workspace.tunnels.miosa.app ``` The tunnel is authenticated server-side (MIOSA workspace), proxied through MIOSA's edge, and emerges on your host via the outbound WebSocket. Same security properties as Preview — short-lived tokens for browser-facing access, no public IP needed on your host. ## Resource control You control what your host shares with MIOSA: ```yaml # ~/.miosa/agent.yaml resources: max_cpu_percent: 50 # cap MIOSA workloads at 50% of CPU max_ram_mb: 8192 # 8 GB ceiling max_disk_gb: 100 schedule: - {days: [Mon-Fri], hours: "09:00-17:00", paused: true} # don't share during work hours ``` The agent reports available capacity to the scheduler in real time. Workloads are gracefully drained off when you go below limits. ## Health and reliability Hosts emit heartbeats every 30s. After ~2 minutes of missed heartbeats, the scheduler marks the host offline and stops placing new work on it. In-flight work either completes (if the host comes back) or is rescheduled to other hosts. For production-critical workloads, run multiple BYOC hosts and let MIOSA spread work across them. ## Security - The agent runs as a regular user (recommended), not root. Computers are Firecracker microVMs the agent spawns; they cannot escape to the host machine. - The outbound WebSocket is authenticated by a host-specific key tied to your tenant. Compromising one host doesn't expose other hosts in your fleet. - The agent is open source (Apache 2.0) — auditable. ## Pricing - **OpenComputers agent: free.** Apache 2.0, run as many hosts as you want. - **MIOSA scheduling / control plane: per-host fee.** Plans range from free (a few personal hosts) to enterprise (hundreds of hosts, fleet management UI). See [Pricing](https://miosa.ai/pricing) for details. ## See also - [Computers / Overview](/docs/computers/overview/) — once registered, BYOC hosts run computers like cloud hosts - [Desktop Control](/docs/computers/desktop/) — drive any computer, BYOC or cloud - [API Reference: OpenComputers](/docs/api-reference/open-computers/) — register / list / manage hosts - [GitHub](https://github.com/Miosa-osa/miosa) — agent source --- # Desktop Control URL: https://miosa.ai/docs/computers/desktop Fallback URL: https://miosa.roberto-c49.workers.dev/docs/computers/desktop Source: src/routes/docs/computers/desktop/+page.md Description: 28 methods for driving a Computer's desktop — screenshot, click, type, scroll, drag, keyboard, clipboard, windows, shell, and more. Once you have a [Computer](/docs/computers/overview/), drive its desktop with these methods. Suitable for AI agents (computer-use models, custom RPA loops) and for direct programmatic control. ## All 28 methods | Group | Method | Purpose | |---|---|---| | **Screen** | `screenshot()` | Capture full desktop as PNG bytes | | | `screenshot_base64()` | Same, base64-encoded — ready to pass to an LLM vision API | | **Click** | `click(x, y)` | Generic click — defaults to left button | | | `left_click(x, y)` | Left-button click at coordinates | | | `right_click(x, y)` | Right-button click (context menu) | | | `double_click(x, y)` | Double-click at coordinates | | **Mouse** | `move_cursor(x, y)` | Move cursor without clicking | | | `mouse_down(x, y)` | Press and hold the mouse button | | | `mouse_up(x, y)` | Release a held mouse button | | | `drag(from_x, from_y, to_x, to_y)` | Click-drag between two coordinates | | **Keyboard** | `type(text)` | Type a string at the current focus | | | `key(key)` | Press a single key (e.g. `"Return"`, `"Escape"`) | | | `hotkey(*keys)` | Simultaneous key combo (e.g. `"ctrl", "c"`) | | | `key_down(key)` | Press and hold a key | | | `key_up(key)` | Release a held key | | **Scroll** | `scroll(direction, clicks)` | Scroll `up`/`down`/`left`/`right` by N notches | | | `scroll_up(clicks)` | Convenience — scroll up | | | `scroll_down(clicks)` | Convenience — scroll down | | | `scroll_left(clicks)` | Convenience — scroll left | | | `scroll_right(clicks)` | Convenience — scroll right | | **Clipboard** | `get_clipboard()` | Read the current clipboard text | | | `set_clipboard(text)` | Write text to the clipboard | | **Screen info** | `get_screen_size()` | Desktop resolution `{width, height}` | | | `get_cursor_position()` | Current cursor `{x, y}` in normalized coords | | **Windows** | `windows()` | List open windows with IDs, titles, positions | | | `launch(app)` | Open an installed application by name | | | `focus_window(id)` | Bring a window to the foreground | | | `get_window_size(id)` | Read a window's `{width, height}` | | | `set_window_size(id, w, h)` | Resize a window | | | `get_window_position(id)` | Read a window's `{x, y}` | | | `set_window_position(id, x, y)` | Move a window | | | `maximize_window(id)` | Maximize a window | | | `minimize_window(id)` | Minimize a window to the taskbar | | | `close_window(id)` | Close a window | | **Environment** | `get_desktop_environment()` | Detect DE name and version (e.g. `xfce4`) | | | `set_wallpaper(path)` | Set the desktop background from a file path | | | `get_accessibility_tree()` | AT-SPI element tree for structured agent perception | | **Shell** | `bash(cmd)` | Execute a shell command inside the VM | | | `python(code)` | Execute a Python snippet inside the VM | | | `write_file(path, content)` | Write content to a file path inside the VM | | | `read_file(path)` | Read a file path inside the VM | ## Coordinate system Computers accept and report coordinates in **normalized 0-1000 space**. (0, 0) is top-left, (1000, 1000) is bottom-right, regardless of the actual display resolution. ```python # Click the visual center of the screen on any display size computer.left_click(500, 500) ``` When you capture a screenshot, MIOSA scales it to a fixed 1024-wide thumbnail by default (`smartResize`). Your agent reasons about coordinates in that normalized space; MIOSA translates to actual display pixels before sending the event to the VM. ## The control loop Diagram: sequenceDiagram participant A as Agent / SDK participant API as Computer API participant envd as envd (port 49983) participant X as X11 desktop A->>API: computer.screenshot() API->>envd: capture frame envd->>X: XGetImage X-->>envd: pixel data envd-->>API: PNG bytes API-->>A: bytes A->>API: computer.left_click(500, 300) API->>envd: inject click event envd->>X: XSendEvent (ButtonPress) X-->>envd: ack envd-->>API: ok API-->>A: None --- ## Screen ### `screenshot()` Capture the full desktop as PNG bytes. ```python png_bytes = computer.screenshot() # Write to disk with open("screen.png", "wb") as f: f.write(png_bytes) ``` Screenshots are PNG. Average 150–300 KB for a 1024-wide thumbnail. ### `screenshot_base64()` Same as `screenshot()`, but returns a base64-encoded string. Use this when feeding the image directly to an LLM vision API. ```python b64 = computer.screenshot_base64() client = anthropic.Anthropic() response = client.messages.create( model="claude-opus-4-5", max_tokens=1024, messages=[{ "role": "user", "content": [ { "type": "image", "source": {"type": "base64", "media_type": "image/png", "data": b64}, }, {"type": "text", "text": "What is on screen? Where should I click to open the browser?"}, ], }], ) ``` --- ## Click ### `click(x, y)` Generic click at coordinates. Defaults to left button. ```python computer.click(640, 480) ``` ### `left_click(x, y)` Explicit left-button click. ```python computer.left_click(640, 480) ``` ### `right_click(x, y)` Right-button click — opens context menus. ```python computer.right_click(640, 480) ``` ### `double_click(x, y)` Double-click — opens files, selects words. ```python computer.double_click(100, 200) ``` --- ## Mouse ### `move_cursor(x, y)` Reposition the cursor without triggering a click. Useful before `mouse_down` / `mouse_up` sequences or for hover interactions. ```python computer.move_cursor(500, 400) ``` ### `mouse_down(x, y)` / `mouse_up(x, y)` Low-level press and release. Use these when `drag` is not granular enough — for example, to hover mid-drag or to implement a long-press interaction. ```python computer.mouse_down(200, 200) computer.move_cursor(400, 400) # move while held computer.mouse_up(400, 400) ``` ### `drag(from_x, from_y, to_x, to_y)` Click-drag between two points with smooth interpolation. Use for sliders, drag-to-select, re-ordering list items. ```python computer.drag(200, 200, 600, 400) ``` --- ## Keyboard ### `type(text)` Type a string as if at a keyboard. Does NOT interpret special key names — use `key()` for those. ```python computer.type("hello, world") ``` ### `key(key)` Press a single key or chord. ```python computer.key("Return") computer.key("Escape") computer.key("ctrl+a") # select all computer.key("alt+F4") # close window ``` Chords use `+` as the separator. Key names follow the X11 keysym convention: `Return`, `Tab`, `BackSpace`, `Delete`, `Home`, `End`, `Page_Up`, `Page_Down`, `Up`, `Down`, `Left`, `Right`, `F1`–`F12`, `super`, `ctrl`, `alt`, `shift`. ### `hotkey(*keys)` Send multiple keys simultaneously — pass each key as a separate argument. ```python computer.hotkey("ctrl", "c") # copy computer.hotkey("ctrl", "v") # paste computer.hotkey("ctrl", "z") # undo computer.hotkey("ctrl", "shift", "t") # reopen tab ``` ### `key_down(key)` / `key_up(key)` Low-level key press and release. Use when you need to hold a modifier while performing other actions. ```python computer.key_down("shift") computer.left_click(800, 200) # shift-click to extend selection computer.key_up("shift") ``` --- ## Scroll ### `scroll(direction, clicks)` Scroll the mouse wheel. `direction` is one of `"up"`, `"down"`, `"left"`, `"right"`. `clicks` is the number of notches. ```python computer.scroll("down", clicks=3) computer.scroll("up", clicks=5) computer.scroll("right", clicks=2) ``` ### Convenience scrolls ```python computer.scroll_up(clicks=3) computer.scroll_down(clicks=3) computer.scroll_left(clicks=2) computer.scroll_right(clicks=2) ``` `clicks` defaults to `1` for all convenience methods. --- ## Clipboard ### `get_clipboard()` Read the current clipboard text content. ```python text = computer.get_clipboard() print(text) ``` ### `set_clipboard(text)` Write text to the clipboard. After this call, `Ctrl+V` inside the VM will paste the text. ```python computer.set_clipboard("some text to paste") computer.hotkey("ctrl", "v") ``` --- ## Screen info ### `get_screen_size()` Returns the desktop resolution as a dict. ```python size = computer.get_screen_size() print(size) # {"width": 1920, "height": 1080} ``` ### `get_cursor_position()` Returns the current cursor position in normalized 0-1000 coordinates. ```python pos = computer.get_cursor_position() print(pos) # {"x": 512, "y": 300} ``` --- ## Windows ### `windows()` List all open windows. Returns a list of dicts with `id`, `title`, `x`, `y`, `width`, `height`, and `focused`. ```python wins = computer.windows() for w in wins: print(w["id"], w["title"], w["focused"]) ``` ### `launch(app)` Open an installed application by name. Available apps depend on the template; `miosa-desktop` ships with Firefox, xterm, Thunar (file manager), and Mousepad (text editor). ```python computer.launch("firefox") computer.launch("xterm") computer.launch("thunar") # file manager ``` ### `focus_window(id)` / `get/set_window_size(id, ...)` / `get/set_window_position(id, ...)` / `maximize_window(id)` / `minimize_window(id)` / `close_window(id)` Full window management via window ID returned by `windows()`. ```python wins = computer.windows() w = wins[0] # Bring to foreground computer.focus_window(w["id"]) # Read geometry size = computer.get_window_size(w["id"]) pos = computer.get_window_position(w["id"]) # Set geometry computer.set_window_size(w["id"], 1280, 800) computer.set_window_position(w["id"], 100, 50) # State changes computer.maximize_window(w["id"]) computer.minimize_window(w["id"]) computer.close_window(w["id"]) ``` --- ## Environment ### `get_desktop_environment()` Returns the active desktop environment name and version. ```python de = computer.get_desktop_environment() print(de) # {"name": "xfce4", "version": "4.18"} ``` ### `set_wallpaper(path)` Set the desktop background image from a path inside the VM. Combine with `write_file` to push a custom image first. ```python # Push wallpaper then apply it computer.write_file("/tmp/bg.png", open("bg.png", "rb").read()) computer.set_wallpaper("/tmp/bg.png") ``` Use `set_wallpaper` together with `write_file` to apply per-tenant branding automatically after computer creation. The wallpaper is stored in the VM's filesystem and persists across stop/start cycles. ### `get_accessibility_tree()` Returns the AT-SPI accessibility tree as a structured dict. Use this for agent perception when coordinates alone are insufficient — for example, to extract button labels, form field names, or reading order without OCR. ```python tree = computer.get_accessibility_tree() # tree is a nested dict of accessible elements # {"role": "frame", "name": "Firefox", "children": [...]} ``` --- ## Shell These methods execute code inside the VM directly — no GUI interaction required. ### `bash(cmd)` Execute a shell command. Returns stdout as a string. ```python output = computer.bash("ls /home/user/Desktop") print(output) # Install a package computer.bash("sudo apt-get install -y curl") # Launch Firefox in background (so the call returns immediately) computer.bash("firefox &") ``` ### `python(code)` Execute a Python snippet inside the VM. Returns stdout. ```python result = computer.python("print(1 + 1)") print(result) # "2" # Multi-line code = """ data = {"status": "ok"} print(json.dumps(data)) """ output = computer.python(code) ``` ### `write_file(path, content)` Write a file into the VM's filesystem. `content` can be a string or bytes. ```python computer.write_file("/home/user/script.py", "print('hello')") computer.bash("python3 /home/user/script.py") ``` ### `read_file(path)` Read a file from the VM's filesystem. Returns the content as a string. ```python content = computer.read_file("/home/user/.bashrc") print(content) ``` --- ## Agent reasoning loop At the application level, an agent drives the computer through a perception-action cycle: Diagram: sequenceDiagram participant AG as Agent participant CM as Computer AG->>CM: screenshot_base64() CM-->>AG: PNG (base64) AG->>AG: reason — what to do next? AG->>CM: left_click(x, y) CM-->>AG: ok AG->>CM: type(text) CM-->>AG: ok AG->>CM: screenshot_base64() CM-->>AG: PNG (base64) AG->>AG: task complete? ## Full agent loop example ```python from miosa import Miosa miosa_client = Miosa(api_key=os.environ["MIOSA_API_KEY"]) claude_client = anthropic.Anthropic() computer = miosa_client.computers.create( name="browser-agent", template="miosa-desktop", size="small", ) computer.start() # Open the browser computer.launch("firefox") # Agent loop for _ in range(10): b64 = computer.screenshot_base64() response = claude_client.messages.create( model="claude-opus-4-5", max_tokens=1024, messages=[{ "role": "user", "content": [ {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": b64}}, {"type": "text", "text": "Navigate to miosa.ai and take a screenshot of the homepage."}, ], }], ) # Parse action from response and dispatch text = response.content[0].text if "left_click" in text: # extract x, y from model output and act computer.left_click(500, 500) elif "type" in text: computer.type("https://miosa.ai\n") computer.stop() ``` --- ## Latency reference Desktop methods run synchronously over authenticated HTTP RPC. Typical round-trip from MIOSA edge: | Method type | Typical latency | |---|---| | Click, key, scroll, type | 30–80 ms | | Screenshot | 100–300 ms (compression-bound) | | Window list | 50–100 ms | | `bash()` / `python()` | 50 ms + command execution time | For high-frequency agent loops, prefer `bash()` for multi-step operations over many individual GUI calls. --- ## Authentication All desktop methods require the same workspace API key as every other MIOSA resource. Call them server-side. For browser-side direct control (computer-use models running in the browser), mint a scoped token — see [Browser Tokens](/docs/platform/browser-tokens/). --- ## See also Pick a template, size, and workspace. Create and start a computer. [Open →](/docs/computers/overview/) Show the live desktop in a browser iframe using short-lived stream tokens. [Open →](/docs/computers/embedding/) Wire format for all 28 methods — request/response shapes, error codes, rate limits. [Open →](/docs/api-reference/desktop/) --- # Embedding & Streaming URL: https://miosa.ai/docs/computers/embedding Fallback URL: https://miosa.roberto-c49.workers.dev/docs/computers/embedding Source: src/routes/docs/computers/embedding/+page.md Description: Embed a live Computer desktop in your own UI via KasmVNC. Pixel streaming, input forwarding. To show a live desktop session in your end-user's browser — for "watch the AI work" UI, manual takeover, or kiosk-style apps — embed MIOSA's KasmVNC stream. ## The flow ``` 1. Your backend asks MIOSA for a terminal ticket for a running computer 2. MIOSA returns a short-lived signed URL pointing at the stream gateway 3. Your frontend renders an iframe with that URL 4. The user sees the live desktop; keyboard/mouse input is forwarded into the VM ``` ## Mint a ticket ```typescript const ticket = await computer.terminalTicket() console.log(ticket.url) // signed stream URL console.log(ticket.expires_at) // ISO timestamp ``` ```bash curl -X POST https://api.miosa.ai/api/v1/computers/$CID/terminal-ticket \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Response: ```json { "token": "miosa_term_...", "url": "https://stream.miosa.app/computer/cmp_xxx?ticket=miosa_term_...", "expires_at": "2026-05-14T19:30:00Z" } ``` `url` is what you embed. `token` is also returned in case you need to construct your own URL (custom stream proxy, etc.). ## Embed ```jsx ``` KasmVNC handles pixel streaming + input forwarding inside the iframe. The user clicks / types in the iframe and those events go to the desktop. ## What's streamed - **Pixels** — encoded video; KasmVNC defaults to a lossy codec tuned for desktop content. - **Cursor position** — overlay rendered client-side. - **Audio** — optional, off by default. Pass `?audio=1` if your app needs it. - **Clipboard** — bidirectional, gated by the iframe's `allow="clipboard-read; clipboard-write"`. ## Read-only mode For "user watches the agent" UI where the end-user shouldn't interact with the desktop: ```typescript const ticket = await computer.terminalTicket({ readOnly: true }) ``` Input events from the iframe are dropped at the stream gateway. Useful for compliance-sensitive flows where only the AI agent should touch the screen. ## Bandwidth Default codec / bitrate is tuned for typical desktop content (~500 Kbps - 2 Mbps). For high-motion content (video playback inside the VM), the codec adapts but expect higher bitrate. The stream gateway is geo-routed to the user's nearest region for lower latency. ## Lifetime Tickets are short-lived (default 1 hour, max 24 hours; configurable per plan). When a ticket expires the stream disconnects; your frontend should watch `expires_at` and re-mint before that. Multiple tickets per computer are fine. Multiple browsers can watch the same desktop simultaneously — they all see the same pixels. ## Custom domains / branded streaming White-label customers can configure `stream.<your-domain>` to front MIOSA's stream gateway, so end users see your brand in the iframe URL. Same DNS / TLS flow as [Deployment Domains](/docs/deploy/domains/), different target. Contact support to set up. ## CSP The stream gateway emits `Content-Security-Policy` allowing the stream to be embedded from approved origins: - Default: `*.miosa.app`, `*.miosa.ai`, `localhost:4000`. - For white-label: add your platform origin via tenant config. If your iframe doesn't render, check the parent page's CSP isn't blocking the iframe and that the stream gateway CSP includes your origin. ## Audit Each ticket issuance emits an audit event with: - The computer ID - The token prefix - Issuing API key - External attribution If a ticket is leaked, you can revoke all outstanding tickets for a computer: ```http DELETE /api/v1/computers/$CID/terminal-tickets ``` ## See also - [Overview](/docs/computers/overview/) — what you're embedding - [Desktop Control](/docs/computers/desktop/) — programmatic control of the same desktop - [Browser Tokens](/docs/platform/browser-tokens/) — analogous pattern for sandbox previews --- # Computers (Desktop) URL: https://miosa.ai/docs/computers/overview Fallback URL: https://miosa.roberto-c49.workers.dev/docs/computers/overview Source: src/routes/docs/computers/overview/+page.md Description: Full Linux desktops in the cloud — Xfce, Firefox, terminal — controlled via 28 Python SDK methods including screenshot, click, type, scroll, clipboard, windows, shell, and more. A **Computer** is a Firecracker microVM running a full Linux desktop (Xfce). Use it when you need a real GUI environment: browser automation, computer-use AI agents, RPA, or screenshot-driven testing. For code execution, dev servers, and most AI agent build loops, [Sandboxes](/docs/develop/sandboxes/) are cheaper and faster. Choose based on whether your workload needs a rendered desktop. ## When to use Computer vs Sandbox | Use case | Pick | |---|---| | Run untrusted code, dev server, `npm install` | **Sandbox** | | Computer-use agent, GUI automation | **Computer** | | Browser automation that needs a real rendered DOM | **Computer** | | AI-agent build loop (code-gen, hot reload, preview) | **Sandbox** | | Screenshot-based UI testing | **Computer** | | Headless Playwright / Selenium | **Sandbox** (cheaper) | | Controlling desktop apps (Figma, Slack, etc.) | **Computer** | ## What is in the box - **Xfce desktop** with Firefox and common Linux apps pre-installed. - **Terminal** — `bash`, `python`, standard CLI toolchain. - **Persistent storage** — files survive stop/start cycles via snapshot restore. - **VNC + WebSocket streaming** — embed the desktop live in a browser via MIOSA's pixel-stream protocol. - **OSA agent** — pre-installed, disabled by default. Activate it for autonomous in-VM AI task execution. ## Quick example ```python client = miosa.Miosa(api_key=os.environ["MIOSA_API_KEY"]) # Create and start a desktop computer computer = client.computers.create(name="agent-1", template="miosa-desktop", size="small") computer.start() # Capture the screen, interact with it png_bytes = computer.screenshot() computer.left_click(640, 480) computer.double_click(100, 200) computer.type("hello world") computer.key("Return") computer.scroll("down", clicks=3) computer.hotkey("ctrl", "c") computer.bash("firefox &") ``` ```typescript const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY! }) // Create and start a desktop computer const computer = await miosa.computers.create({ name: "agent-1", templateType: "miosa-desktop", size: "small", }) await computer.start() // Capture the screen, interact with it const png = await computer.screenshot() await computer.leftClick(640, 480) await computer.doubleClick(100, 200) await computer.type("hello world") await computer.key("Return") await computer.scroll({ direction: "down", clicks: 3 }) await computer.hotkey("ctrl", "c") await computer.bash("firefox &") ``` ## Desktop action reference All 28 methods available on a running computer: | Group | Method | Description | |---|---|---| | **Screen** | `screenshot()` | Capture full desktop as PNG bytes | | | `screenshot_base64()` | Same, base64-encoded — ready for LLM vision APIs | | **Click** | `click(x, y)` | Generic click (defaults to left button) | | | `left_click(x, y)` | Left-button click | | | `right_click(x, y)` | Right-button click (context menu) | | | `double_click(x, y)` | Double-click | | **Mouse** | `move_cursor(x, y)` | Move cursor without clicking | | | `mouse_down(x, y)` | Press and hold the mouse button | | | `mouse_up(x, y)` | Release a held mouse button | | | `drag(from_x, from_y, to_x, to_y)` | Click-drag between two coordinates | | **Keyboard** | `type(text)` | Type a string at the current focus | | | `key(key)` | Send a single key (e.g. `"Return"`, `"ctrl+a"`) | | | `hotkey(*keys)` | Simultaneous key combo (e.g. `"ctrl", "c"`) | | | `key_down(key)` | Press and hold a key | | | `key_up(key)` | Release a held key | | **Scroll** | `scroll(direction, clicks)` | Scroll `up`/`down`/`left`/`right` | | | `scroll_up/down/left/right(clicks)` | Convenience scroll methods | | **Clipboard** | `get_clipboard()` | Read clipboard text | | | `set_clipboard(text)` | Write text to clipboard | | **Screen info** | `get_screen_size()` | Desktop resolution `{width, height}` | | | `get_cursor_position()` | Current cursor `{x, y}` in normalized coords | | **Windows** | `windows()` | List open windows with IDs, titles, positions | | | `launch(app)` | Open an installed app by name | | | `focus_window(id)` | Bring a window to the foreground | | | `get/set_window_size(id, ...)` | Read or set window dimensions | | | `get/set_window_position(id, ...)` | Read or set window position | | | `maximize/minimize/close_window(id)` | Change window state | | **Environment** | `get_desktop_environment()` | DE name and version (e.g. `xfce4`) | | | `set_wallpaper(path)` | Set desktop background from a VM file path | | | `get_accessibility_tree()` | AT-SPI element tree for structured agent perception | | **Shell** | `bash(cmd)` | Execute a shell command inside the VM | | | `python(code)` | Execute a Python snippet inside the VM | | | `write_file(path, content)` | Write a file into the VM's filesystem | | | `read_file(path)` | Read a file from the VM's filesystem | See [Desktop Control](/docs/computers/desktop/) for full examples, parameter details, and coordinate system documentation. ## Workspace-scoped creation Computers can be created inside a named workspace so that resources are isolated and billed separately. Pass `external_workspace_id` to attribute the computer to a tenant in your platform. ```python client = miosa.Miosa(api_key=os.environ["MIOSA_API_KEY"]) computer = client.computers.create( name="agent-1", template="miosa-desktop", size="small", external_workspace_id="acme-corp", # your tenant identifier metadata={"agent_run": "run-2026-05-17"}, ) computer.start() ``` ```typescript const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY! }) const computer = await miosa.computers.create({ name: "agent-1", templateType: "miosa-desktop", size: "small", externalWorkspaceId: "acme-corp", metadata: { agentRun: "run-2026-05-17" }, }) await computer.start() ``` `external_workspace_id` is a free-form string you control — use your own tenant/org identifier. Usage is tracked per workspace in the billing dashboard so you can attribute compute costs to individual customers. ## White-label desktops Computers support per-tenant desktop branding. After starting a computer, push a wallpaper and apply it with two SDK calls. The wallpaper persists across stop/start cycles because it is stored in the VM's snapshotted filesystem. ```python client = miosa.Miosa(api_key=os.environ["MIOSA_API_KEY"]) computer = client.computers.create( name="acme-agent", template="miosa-desktop", size="small", external_workspace_id="acme-corp", ) computer.start() # Push the tenant wallpaper from your server's filesystem computer.write_file("/tmp/acme-wallpaper.png", open("assets/acme-bg.png", "rb").read()) computer.set_wallpaper("/tmp/acme-wallpaper.png") # Optionally verify the DE de = computer.get_desktop_environment() print(de) # {"name": "xfce4", "version": "4.18"} ``` ```typescript const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY! }) const computer = await miosa.computers.create({ name: "acme-agent", templateType: "miosa-desktop", size: "small", externalWorkspaceId: "acme-corp", }) await computer.start() // Push and apply the tenant wallpaper const bg = fs.readFileSync("assets/acme-bg.png") await computer.writeFile("/tmp/acme-wallpaper.png", bg) await computer.setWallpaper("/tmp/acme-wallpaper.png") ``` For production deployments, bake the wallpaper directly into a custom template snapshot so every new computer boots with the correct branding — no post-boot setup required. See the Templates documentation for details. ## How it fits together Diagram: graph LR SDK["Python / TypeScript SDK"] -->|REST| API["MIOSA API"] API --> VM["Firecracker VM\n(Xfce desktop)"] VM --> envd["envd agent\n(port 49983)"] envd -->|X11 input| Desktop["Desktop session"] envd -->|PNG frame| Stream["Pixel stream"] Stream -->|WebSocket| Browser["Browser iframe"] Your code talks to the MIOSA API. MIOSA routes actions to `envd` running inside the VM. `envd` drives the X11 session and returns screenshots or action confirmations. For embedded views, MIOSA streams the desktop over WebSocket to a browser iframe. ## Embed the desktop in a browser Mint a stream token from your backend, then pass the URL to a browser iframe: ```python token = computer.stream_token() # token["url"] → short-lived WebSocket stream URL # Pass this URL to your frontend; do not embed your API key. ``` ```typescript const token = await computer.streamToken() // token.url → short-lived WebSocket stream URL // ``` Never put `msk_*` in a `