# MIOSA Documentation - Full Text Generated: 2026-09-01T10:08:50.143Z 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. ## Authoritative Agent Contract Guide: https://miosa.ai/docs/guides/agent-operating-guide - Run `miosa capabilities --json`, `miosa app inspect`, and `miosa app plan` before reconstructing a workflow from old examples. - Resolve one exact organization, workspace, application, and environment before mutating resources. - Reuse the linked persistent sandbox unless it is proven terminal. Do not replace resources because an identifier or route was not resolved. - Use `miosa app preview` to create and test an immutable candidate release. Preview never promotes production. - Verify and promote one exact release ID. Never promote an inferred latest release. - Call a release live only when the active release, running artifact digest, route contract, placement, data bindings, and required configuration agree. - Recover interrupted operations with the recorded operation ID and idempotency key instead of starting duplicates. - Use URLs returned by MIOSA. Do not construct preview, deployment, or custom-domain hostnames. --- # 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, deploy, prove, and operate AI-generated applications on MIOSA sandboxes, computers, App Engine, and white-label domains. ## Explore the platform Run many workspace apps as containers on one always-on App Engine host, with MIOSA-owned routing, domains, and proof. Dispatch Claude Code, Codex, or command runners to sandboxes and computers, then stream events and collect artifacts. Follow the authoritative discovery, preview, verification, promotion, recovery, and rollback contract for automated workers. Use REST, CLI, TypeScript, Python, Go, Java, Elixir, MCP, and ADK surfaces that share the same deployment proof contract. Attach Postgres, storage, Redis, volumes, and durable application state without rebuilding the infrastructure layer. ## Core infrastructure Use isolated Linux environments for generated code, dependency installs, file edits, build commands, tests, and preview servers. Publish generated applications to stable URLs with versions, releases, environment variables, dynamic runtimes, App Engine, and rollback paths. Put previews, deployments, App Engine URLs, browser tokens, customer attribution, and custom domains behind your own product model. Build the server-side control plane that dispatches prompts into sandboxes, computers, local devices, and Agent Runs. Orchestrate MIOSA-managed compute, connected machines, AWS, Google Cloud, and hybrid placement through one resource model. Give agents a full Linux desktop when they need browsers, screenshots, clicks, uploads, form fills, or human handoff. --- ## What you can build Build a branded prompt-to-app platform where agents generate full-stack software, stream their work, open live previews, manage versions, and publish production deployments. Coordinate sandbox agents, computer agents, artifact generators, browser QA, deployment agents, and live UI events. Add a backend that resolves users, dispatches prompts, streams agent work, stores artifacts, and enforces quota. Build autonomous company operators, agent departments, persistent virtual devices, miniapps, jobs, approvals, and connected-tool workflows under your own brand. Give each customer isolated workspaces, branded preview domains, deployment domains, custom domains, browser tokens, and usage attribution. Turn prompts into published landing pages, intake forms, calculators, content hubs, and campaign sites without exposing MIOSA. Run apps that need a server process, API routes, background work, environment variables, and persistent release management. Execute user code in isolated environments, stream stdout and stderr, capture artifacts, and shut work down when finished. Use Computers for real browser sessions: navigate, click, type, upload files, capture screenshots, and hand control to users. Bring third-party computer capacity into MIOSA while keeping one API, one product surface, and one user relationship. Measure sandbox lifecycles, desktop readiness, screenshot/action latency, and task success across agent workflows. --- ## Platform guides --- ## Developer surfaces | Surface | Use it when | |---|---| | [REST API](/docs/api-reference/) | You want complete control over sandboxes, files, exec, previews, deployments, computers, domains, tokens, and events. | | [Backend orchestration](/docs/integrations/backend-orchestration/) | You are building the server-side layer that dispatches prompts into MIOSA devices and streams work back to your UI. | | [CLI](/docs/cli/) | You want to create, inspect, publish, and debug resources from a terminal. | | [Python SDK](/docs/sdks/python/) | You are building agent loops, backend orchestration, or data workflows in Python. | | [TypeScript SDK](/docs/sdks/typescript/) | You are building Node services, web backends, or product integrations in TypeScript. | | [Go SDK](/docs/sdks/go/) | You are building backend services that need typed deployment, App Engine, and proof helpers. | | [MCP server](/docs/mcp/) | You want an AI agent to use MIOSA tools through the Model Context Protocol. | | [Agent Development Kit](/docs/adk/) | You want higher-level agent building blocks on top of the raw sandbox and deployment APIs. | --- ## For LLMs MIOSA publishes machine-readable docs indexes so agents can retrieve current public documentation without scraping the site. --- ## Current production contract Use URLs returned by the API instead of constructing hostnames yourself: - `preview_url` for sandbox previews. - `desktop_url` for computer desktops. - `public_url` for deployments. - `custom_domain` or deployment-domain URLs when a workspace has branded domains configured. - `docker_deploy_app` in deployment responses as App Engine container proof. For App Engine, a deployment is not proven live until it has a normal Deployment row, `deployment_product: "docker_deploy"`, an active workspace App Engine host, a `docker_deploy_app` row, a running container route, and an HTTP response from `public_url`. See the [changelog](/docs/changelog/) for the latest public platform behavior. --- # Agent Development Kit (ADK) URL: https://miosa.ai/docs/adk Fallback URL: https://miosa.roberto-c49.workers.dev/docs/adk Source: src/routes/docs/adk/+page.md Description: Build LLM agents that use MIOSA sandboxes, desktops, and deploys with the tool-call loop already wired. TypeScript ADK first; Python next. # Agent Development Kit (ADK) The MIOSA ADK lets you build production-ready LLM agents that natively use MIOSA primitives - sandboxes, full desktop environments, and one-click deploys - with the tool-call loop already wired. If `@miosa/sdk` is "call MIOSA APIs from code", `@miosa/adk` is **"build an AGENT that uses MIOSA"**. ```bash npm install @miosa/adk @anthropic-ai/sdk ``` --- ## 30-second example ```typescript const agent = new Agent({ provider: anthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: "claude-opus-4-7", }), miosaApiKey: process.env.MIOSA_API_KEY!, }); const result = await agent.run({ prompt: "Create a MIOSA sandbox, write a Python hello-world, run it, then destroy the sandbox.", maxIterations: 10, }); console.log(result.finalText); ``` That's it. The agent boots a sandbox, writes the file, runs it, prints output, destroys the sandbox, returns the final response. No tool-loop boilerplate. --- ## ADK vs SDK The raw TypeScript SDK around MIOSA's REST API. You call methods: ```typescript const computer = await client.computers.create({ name: "x" }); await computer.exec.bash("echo hello"); ``` Use this when you're **writing application code** that talks to MIOSA. Adds the agent loop on top. You give it a provider + prompt: ```typescript const agent = new Agent({ provider, miosaApiKey }); await agent.run({ prompt: "build a flask app and run it" }); ``` Use this when you're **building an agent** that should use MIOSA. --- ## Built-in MIOSA tools When you pass `miosaApiKey`, the agent gets these tools out of the box: | Tool | Does | |------|------| | `create_sandbox` | Boot a Debian 12 microVM (Python + Node). | | `create_desktop` | Start a full desktop environment. | | `list_sandboxes` | List your active computers. | | `get_sandbox` | Fetch one sandbox by id (status / IP). | | `destroy_sandbox` | Delete a computer. | | `exec` | Run a bash command. | | `exec_python` | Run a Python snippet inline. | | `read_file` | Read a file from the VM. | | `write_file` | Write a file inside the VM. | | `list_files` | List a directory inside the VM. | Get an API key at , or run `npx -y @miosa/cli mcp install` to wire one via browser-based auth. --- ## Custom tools Compose your own tool catalogue alongside (or instead of) the MIOSA tools: ```typescript Agent, anthropicProvider, miosaTools, type Tool, } from "@miosa/adk"; const miosa = new Miosa({ apiKey: process.env.MIOSA_API_KEY! }); const sendSlack: Tool = { name: "send_slack", description: "Post a message to the #builds Slack channel.", inputSchema: { type: "object", properties: { text: { type: "string" } }, required: ["text"], }, async execute(args) { const text = String(args["text"] ?? ""); // your Slack call here return `Sent: ${text}`; }, }; const agent = new Agent({ provider: anthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }), tools: [...miosaTools({ client: miosa }), sendSlack], }); ``` --- ## Streaming step events Drive a live UI as the agent runs: ```typescript const result = await agent.run({ prompt: "...", onStep: (step) => { console.log(`[turn ${step.index}] ${step.text}`); for (const call of step.toolCalls) { console.log( ` → ${call.name}(${JSON.stringify(call.input)}) → ${call.output.slice(0, 80)}`, ); } }, }); ``` The `AgentStep` shape: ```typescript interface AgentStep { index: number; text: string; toolCalls: Array<{ id: string; name: string; input: Record; output: string; isError: boolean; }>; usage?: { inputTokens: number; outputTokens: number }; } ``` --- ## Providers `@miosa/adk` ships with native tool-call providers for hosted, marketplace, and local OpenAI-compatible models, plus Anthropic and Gemini-specific adapters. The same provider interface also works for custom internal routers. ```typescript const provider = anthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY!, model: "claude-opus-4-7", // default maxTokens: 4096, // default }); ``` Uses `@anthropic-ai/sdk` Messages API with native tool-use. Supports any Claude model that supports tool calling. ```typescript const provider = surplusProvider({ apiKey: process.env.SURPLUS_API_KEY!, model: "claude-opus-4.8", }); ``` Surplus uses the OpenAI-compatible chat completions contract. In platform runtime profiles, prefer routing through MIOSA's Intelligence API/model route so keys can be metered, audited, revoked, and used as fallback fuel for Claude Code, Codex, Hermes, OSA, or custom harnesses inside MIOSA sandboxes. ```typescript const provider = miosaRouteProvider({ miosaApiKey: process.env.MIOSA_API_KEY!, model: "optimal", }); ``` Use this when the agent should call MIOSA's Intelligence API rather than a third-party provider directly. As model routes ship, the model can become a route id such as `route:cheap-claude-code`; MIOSA then owns provider selection, fallbacks, metering, and secret handling. ```typescript const openai = openAIProvider({ apiKey: process.env.OPENAI_API_KEY!, model: "gpt-5", }); const groq = groqProvider({ apiKey: process.env.GROQ_API_KEY!, model: "moonshotai/kimi-k2-instruct-0905", }); const internal = openAICompatibleProvider({ name: "internal-router", baseUrl: "https://llm.example.com/v1/chat/completions", apiKey: process.env.INTERNAL_LLM_KEY, model: "best-agent-model", }); ``` The bundled OpenAI-compatible provider covers OpenAI, Groq, DeepSeek, OpenRouter, Together, Fireworks, Mistral, Cerebras, Perplexity, xAI, Surplus, MIOSA Intelligence, Ollama, LM Studio, and custom routers. Implement the `Provider` interface directly when an endpoint needs a custom tool or streaming protocol. --- ## Termination `agent.run()` returns when: - The model emits an `end_turn` with no further tool calls (normal completion) - A `stop_sequence` is hit - `maxIterations` is reached (default 10) - safety guard against runaway loops - The provider throws (returned as `stopReason: "error"`) ```typescript const result = await agent.run({ prompt: "...", maxIterations: 20 }); if (result.stopReason === "max_iterations") { console.warn("Agent didn't finish in 20 turns"); } ``` --- ## Quick comparison | | `@miosa/adk` | Anthropic Claude Agent SDK | OpenAI Agents SDK | Google ADK | |--|--|--|--|--| | Tool-call loop | ✓ | ✓ | ✓ | ✓ | | MIOSA tools built-in | **✓** | - | - | - | | Anthropic provider | ✓ | ✓ | - | - | | OpenAI-compatible providers | ✓ | - | ✓ | - | | Sub-agents | (roadmap) | ✓ | ✓ | ✓ | | Memory | (roadmap) | ✓ | (basic) | ✓ | | Cost | free | free | free | free | The ADK is intentionally small. If you want MIOSA-as-execution-substrate with a clean agent loop, you're in the right place. --- ## What's coming next - **Python ADK** (`pip install miosa-adk`) - same surface, Python ergonomics. - **Managed model routes** - route ADK and in-sandbox harness calls through MIOSA Intelligence API. - **Sub-agents** - spawn child agents with scoped tool subsets. - **Memory** - built-in conversation memory with TTL + summarization. - **Streaming output** - token-by-token via provider.streamStep. Open an issue at if there's something you need sooner. --- # 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 the allowlisted MIOSA Sandbox V1 API interactively. Use the written endpoint guides for the wider platform API. This interactive reference covers the allowlisted Sandbox V1 OpenAPI surface. Use the written API reference for deployments, databases, computers, and the wider platform contract. {#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 endpoint guides. Want to browse and try endpoints without writing code? The Interactive Sandbox API Reference renders the allowlisted public Sandbox V1 OpenAPI contract with an in-browser request runner. Use the endpoint guides below for the wider platform API. 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_u_... ``` 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_u_... 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 - command service unreachable | ## Rate limits Rate limits apply per API key, tenant or organization, and endpoint family. The current self-service defaults are 600 requests per minute for user keys and 30,000 requests per minute for platform keys. Specific endpoint families and tenant policies can impose lower limits. Treat `X-RateLimit-*` and `Retry-After` response headers as authoritative. ## IDs and timestamps Canonical resource IDs returned by the current API are UUIDs. Some older SDK examples and compatibility surfaces may still show human-readable aliases. Pass the exact ID returned by the API instead of constructing or prefixing an identifier. 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/) Dispatch prompts into sandboxes or computers through OSA, Codex, Claude Code, Pi, Hermes, or a custom runtime. [Open →](/docs/api-reference/agent-runs/) Configure reusable agent runtimes, tools, connector grants, policy, and non-secret env defaults. [Open →](/docs/api-reference/agent-runtime-profiles/) 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/) Provision managed databases and operate lifecycle, backups, restores, branches, replicas, credentials, logs, and metrics. [Open →](/docs/api-reference/databases/) 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/) SDK coverage varies by language and release. Use the current SDK page for your language to confirm a helper exists. You can always call the documented REST endpoint directly. --- # Agent Run Groups URL: https://miosa.ai/docs/api-reference/agent-run-groups Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/agent-run-groups Source: src/routes/docs/api-reference/agent-run-groups/+page.md Description: Create durable multi-agent orchestration groups, dispatch run manifests, inspect counts, and cancel grouped work. # Agent Run Groups Agent Run Groups are the durable control-plane object for multi-agent fanout. Use a group when one user request creates many related Agent Runs across sandboxes, computers, or custom runtimes. Base path: `/api/v1/agent-run-groups` Use synchronous dispatch for small fanout where you want child run creation in the response. Use `async: true` for queued fanout: MIOSA stores each manifest entry, enforces the group's concurrency limit while workers claim entries, and returns immediately with queued entry records. ## Endpoints | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/agent-run-groups` | List groups for the tenant | | `POST` | `/api/v1/agent-run-groups` | Create a group | | `GET` | `/api/v1/agent-run-groups/{id}` | Fetch one group | | `POST` | `/api/v1/agent-run-groups/{id}/dispatch` | Dispatch a bounded manifest of child Agent Runs | | `GET` | `/api/v1/agent-run-groups/{id}/events` | Replay or stream durable events from every child run in the group | | `POST` | `/api/v1/agent-run-groups/{id}/cancel` | Cancel the group and running children | ## Create ```http POST /api/v1/agent-run-groups Authorization: Bearer msk_u_... Content-Type: application/json ``` ```json { "name": "clinic-iq-browser-checks", "workspace_id": "ws_123", "project_id": "proj_123", "concurrency_limit": 25, "expected_runs": 100, "metadata": { "user_task_id": "task_123" } } ``` ## Dispatch Dispatch accepts a `runs` array. Each entry uses the same fields as [Agent Runs](/docs/api-reference/agent-runs/), plus optional `orchestration_role` and `parent_agent_run_id`. ```http POST /api/v1/agent-run-groups/{id}/dispatch Authorization: Bearer msk_u_... Content-Type: application/json ``` ```json { "runs": [ { "sandbox_id": "sbx_code_1", "provider": "claude-code", "cwd": "/workspace", "prompt": "Implement the pricing page and export /workspace/report.html", "orchestration_role": "coder", "metadata": { "artifact_paths": ["/workspace/report.html"] } }, { "computer_id": "cmp_browser_1", "provider": "osa", "prompt": "Open the preview, test signup, and capture screenshots.", "orchestration_role": "browser-qa" } ], "async": true } ``` Set `async` to `true` when the group should queue entries and return before the child Agent Runs are created. MIOSA creates one durable entry per manifest item, then dispatcher workers claim entries under the group's `concurrency_limit`. Current dispatch batches are capped at `100` entries per request. For larger fleets, shard logical work into multiple groups or dispatch batches from your backend. ## Response ```json { "data": { "group": { "id": "grp_123", "name": "clinic-iq-browser-checks", "status": "running", "concurrency_limit": 25, "expected_runs": 100, "counts": { "total": 2, "running": 0, "succeeded": 2, "failed": 0, "canceled": 0 }, "entry_counts": { "total": 2, "queued": 0, "running": 0, "succeeded": 2, "failed": 0, "canceled": 0 } }, "results": [ { "index": 0, "ok": true, "run": { "id": "run_123", "agent_run_group_id": "grp_123", "orchestration_role": "coder", "status": "succeeded" } } ], "entries": [] } } ``` Async dispatch returns `entries` immediately and usually returns an empty `results` array: ```json { "data": { "group": { "id": "grp_123", "status": "running", "concurrency_limit": 25, "entry_counts": { "total": 2, "queued": 2, "running": 0, "succeeded": 0, "failed": 0, "canceled": 0 } }, "results": [], "entries": [ { "id": "agre_123", "agent_run_group_id": "grp_123", "index": 0, "status": "queued", "attempts": 0 } ] } } ``` ## Events Use group events when your UI needs one stream for an entire multi-agent task. MIOSA records child Agent Run events and replays them through the group in sequence order. This lets your product render progress from the coder, tester, browser QA agent, and deployment verifier without opening one stream per child run. Replay existing events as JSON: ```http GET /api/v1/agent-run-groups/{id}/events Authorization: Bearer msk_u_... ``` Stream existing events plus live events with SSE: ```http GET /api/v1/agent-run-groups/{id}/events?stream=true Accept: text/event-stream Authorization: Bearer msk_u_... ``` Event payloads include the group id, child run id, event sequence, event type, message, payload, and timestamp: ```json { "data": [ { "id": "ev_123", "agent_run_group_id": "grp_123", "agent_run_id": "run_123", "sequence": 4, "type": "command_finished", "message": "Command completed", "payload": { "exit_code": 0 }, "created_at": "2026-06-15T21:20:00Z" } ] } ``` Terminal group states close the SSE response after replay. Running groups keep the stream open and emit heartbeat comments while waiting for new child events. ## SDK ```typescript const group = await miosa.agentRunGroups.create({ name: "clinic-iq-browser-checks", workspaceId: "ws_123", concurrencyLimit: 25, expectedRuns: 100, }); const result = await miosa.agentRunGroups.dispatch( group.id, [ { sandboxId: "sbx_code_1", provider: "claude-code", cwd: "/workspace", prompt: "Implement the pricing page and export /workspace/report.html", orchestrationRole: "coder", metadata: { artifact_paths: ["/workspace/report.html"] }, }, ], { async: true }, ); const withRuns = await miosa.agentRunGroups.get(group.id, { includeRuns: true }); const events = await miosa.agentRunGroups.events(group.id); for await (const event of miosa.agentRunGroups.streamEvents(group.id)) { console.log(event.agent_run_id, event.type, event.message); } ``` ```python group = client.agent_run_groups.create( name="clinic-iq-browser-checks", workspace_id="ws_123", concurrency_limit=25, expected_runs=100, ) result = client.agent_run_groups.dispatch( group["id"], [ { "sandbox_id": "sbx_code_1", "provider": "claude-code", "cwd": "/workspace", "prompt": "Implement the pricing page and export /workspace/report.html", "orchestration_role": "coder", "metadata": {"artifact_paths": ["/workspace/report.html"]}, } ], async_=True, ) with_runs = client.agent_run_groups.get(group["id"], include_runs=True) events = client.agent_run_groups.events(group["id"]) for event in client.agent_run_groups.stream_events(group["id"]): print(event["agent_run_id"], event["type"], event.get("message")) ``` ## CLI ```bash miosa agent-run-groups create \ --name "clinic-iq-browser-checks" \ --workspace ws_123 \ --concurrency 25 \ --expected-runs 100 \ --json miosa agent-run-groups dispatch grp_123 \ --run '{"sandbox_id":"sbx_code_1","provider":"claude-code","prompt":"Build and export report","metadata":{"artifact_paths":["/workspace/report.html"]}}' \ --async \ --json miosa agent-run-groups show grp_123 --runs --json miosa agent-run-groups events grp_123 --json miosa agent-run-groups events grp_123 --stream --limit 100 miosa agent-run-groups cancel grp_123 --json ``` ## Next Dispatch one prompt into a sandbox or computer and collect events and artifacts. Design the product backend that assigns devices, streams progress, and stores outputs. Configure inherited environment variables for agents and deployments. --- # Agent Runs URL: https://miosa.ai/docs/api-reference/agent-runs Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/agent-runs Source: src/routes/docs/api-reference/agent-runs/+page.md Description: Dispatch prompts into MIOSA devices with a stable run response shape for agent work. # Agent Runs Agent Runs are the prompt-dispatch interface for MIOSA sandboxes and computers. Use them when your product needs to send a natural-language task into a device, run it through OSA, Codex, Claude Code, Pi, Hermes, or a custom runtime, and receive a stable run response your UI can render. This is the API behind: - `miosa agent run --sandbox ...` - `miosa agent run --computer ...` - `miosa sandbox prompt` - `client.agentRuns.run(...)` in the TypeScript SDK - `client.agent_runs.run(...)` in the Python SDK - `miosa runs files ` - `miosa runs download-file ` Use raw exec when you already know the command. Use Agent Runs when the user prompt should be handled by an agent runtime inside a MIOSA device. The run response preserves prompt, provider, target, output, error, metadata, and timestamps for your product UI and audit trail. ## Flow ## Create a run ```http POST /api/v1/agent-runs Authorization: Bearer msk_u_... Content-Type: application/json ``` `POST /agent-runs` is live for sandbox and computer targets. Use sandbox targets for code, files, tests, builds, artifacts, and preview servers. Use computer targets when the task needs a browser, desktop, screenshots, or interactive UI state. For customer-owned OpenComputers hosts, use `POST /opencomputers/hosts/{id}/agent/dispatch` or `miosa agent run --host `. ```json { "target_kind": "sandbox", "target_id": "41026070-9bb0-4d62-90b4-8ceeb0a131b6", "provider": "claude-code", "prompt": "Update the landing page, run tests, and leave the preview running.", "cwd": "/workspace", "timeout": 1800, "metadata": { "workspace_id": "clinic-iq", "task_id": "task_123" } } ``` ### Request fields | Field | Type | Required | Description | |---|---:|---:|---| | `prompt` | string | yes | Natural-language task to run. | | `target_id` | UUID/string | yes | Sandbox or computer id. | | `target_kind` | `sandbox` / `computer` | recommended | Target family. Defaults are inferred when using `sandbox_id` or `computer_id`. | | `sandbox_id` | UUID/string | no | Shortcut for `target_id` with sandbox target. | | `computer_id` | UUID/string | no | Shortcut for `target_id` with computer target. | | `provider` | string | no | `osa`, `codex`, `claude`, `claude-code`, `pi`, `hermes`, or `custom`. | | `command` | string | required for `custom` | Runtime command for custom providers. | | `model` | string | no | Provider-specific model override. | | `cwd` | string | no | Working directory, usually `/workspace`. | | `timeout` | integer | no | Run timeout in seconds. | | `env` | object | no | Non-secret run env overrides. Profile env is applied first. | | `agent_runtime_profile_id` | UUID/string | no | Force a specific runtime profile for this run. Alias: `agent_profile_id`. | | `agent_run_group_id` | UUID/string | no | Attach this run to an Agent Run Group. | | `parent_agent_run_id` | UUID/string | no | Link this run to a parent/orchestrator run. | | `orchestration_role` | string | no | Product role such as `coder`, `browser-qa`, `tester`, or `artifact-generator`. | | `skip_agent_runtime_profile` | boolean | no | If `true`, skip tenant/workspace default runtime profile resolution. | | `metadata` | object | no | Product ids, UI labels, task ids, or trace metadata. | ## Response ```json { "data": { "id": "b1772e2f-89d1-4b59-9aa7-147eed4c902a", "agent_run_group_id": "grp_123", "parent_agent_run_id": null, "orchestration_role": "coder", "target_kind": "sandbox", "target_id": "41026070-9bb0-4d62-90b4-8ceeb0a131b6", "provider": "claude-code", "prompt": "Update the landing page, run tests, and leave the preview running.", "status": "succeeded", "output": "Updated app/page.tsx and started preview on port 3000.", "stderr": "", "exit_code": 0, "metadata": { "workspace_id": "clinic-iq", "task_id": "task_123" }, "started_at": "2026-06-14T10:30:00Z", "finished_at": "2026-06-14T10:31:42Z", "created_at": "2026-06-14T10:30:00Z", "updated_at": "2026-06-14T10:31:42Z" } } ``` ### Status values | Status | Meaning | |---|---| | `running` | Reserved for async run history. | | `succeeded` | The runtime completed successfully. | | `failed` | The runtime returned a non-zero exit or backend error. | | `canceled` | The run was canceled. For sandbox process-backed runs, MIOSA also stops the recorded sandbox process. | ## Artifacts Agent runs can record generated files as artifacts. MIOSA captures declared artifact bytes into managed storage when possible, then falls back to the live target filesystem for older or unpersisted artifacts. That matters for product UX: if `persisted` is `true`, your users can download the file even after the sandbox or computer session has stopped. If `persisted` is `false`, keep the target running until the file has been downloaded, exported, or published. Declare artifact paths in the request body: ```json { "sandbox_id": "41026070-9bb0-4d62-90b4-8ceeb0a131b6", "provider": "claude-code", "prompt": "Create /workspace/report.html", "metadata": { "artifact_paths": ["/workspace/report.html"] } } ``` List artifacts: ```http GET /api/v1/agent-runs/{run_id}/artifacts Authorization: Bearer msk_u_... ``` Download one artifact: ```http GET /api/v1/agent-runs/{run_id}/artifacts/{artifact_id}/download Authorization: Bearer msk_u_... ``` Use `?disposition=inline` when embedding an HTML/PDF/image artifact in your own preview UI. The default response is an attachment. Artifact list responses also include short-lived `signed_download_url` values when the backend can mint them. Use these URLs for browser-facing downloads in your own UI; they do not require exposing the user's API key. Artifact records include persistence metadata: | Field | Meaning | |---|---| | `status` | Artifact lifecycle status such as `created`, `available`, or `failed`. | | `sha256` | Content hash when MIOSA captured bytes. | | `persisted` | `true` when the bytes are stored outside the live runtime. | | `storage_backend` | Storage backend label, for example `s3` or `local`. | | `persisted_at` | Timestamp when MIOSA captured the bytes. | MIOSA does not expose internal storage keys or bucket URLs in the public API. Use the download endpoint or signed download URL instead. ## Events Agent Run events are durable. Use them to build timelines, progress feeds, audit trails, and orchestration UIs. | Event | Meaning | |---|---| | `created` | Run record was created. | | `command_started` | MIOSA started executing the runtime command. | | `process_started` | Sandbox process-backed run started and recorded a sandbox process id. | | `command_finished` | Runtime command finished and the run was marked succeeded or failed. | | `artifacts_recorded` | Declared artifacts were captured or recorded. | | `failed` | Run failed before normal command completion. | | `canceled` | Run was canceled. | List durable events: ```http GET /api/v1/agent-runs/{run_id}/events Authorization: Bearer msk_u_... ``` Stream events with SSE: ```http GET /api/v1/agent-runs/{run_id}/events?stream=true Accept: text/event-stream Authorization: Bearer msk_u_... ``` The stream sends existing durable events first, then live PubSub events. Terminal events such as `command_finished`, `failed`, and `canceled` close the stream. ## Cancellation ```http POST /api/v1/agent-runs/{run_id}/cancel Authorization: Bearer msk_u_... ``` Cancellation is best-effort by target type: | Target | Behavior | |---|---| | Sandbox process-backed run | MIOSA stops the recorded sandbox process, marks the run `canceled`, and emits `canceled`. | | Older synchronous sandbox path | MIOSA marks the run `canceled`; the command may already have completed by the time cancellation is received. | | Computer/BYOC target | MIOSA marks the run `canceled`; computer runtime interruption depends on the target agent implementation. | For sandbox process-backed runs, the run metadata includes: ```json { "agent_process": { "target_kind": "sandbox", "target_id": "41026070-9bb0-4d62-90b4-8ceeb0a131b6", "process_id": "proc_123", "pid": 123, "started_at": "2026-06-15T16:45:00Z" } } ``` ## Run history Use the response from `POST /agent-runs` for the immediate task result. For long-running browser or desktop conversations, keep using the Computer session APIs alongside Agent Runs so your UI can show both the interactive session and the per-prompt run result. ## SDK examples ```typescript const client = new Miosa({ apiKey: process.env.MIOSA_API_KEY! }); const run = await client.agentRuns.run({ sandboxId: "41026070-9bb0-4d62-90b4-8ceeb0a131b6", targetKind: "sandbox", agentRunGroupId: "grp_123", orchestrationRole: "coder", agentRuntimeProfileId: "arp_123", provider: "claude-code", cwd: "/workspace", timeout: 1800, prompt: "Build the dashboard, run tests, and keep the preview live.", metadata: { artifact_paths: ["/workspace/report.html"], }, }); console.log(run.id, run.status, run.output); const artifacts = await client.agentRuns.artifacts(run.id); const report = await client.agentRuns.downloadArtifact(run.id, artifacts[0].id); ``` ```python from miosa import Miosa client = Miosa() run = client.agent_runs.run( sandbox_id="41026070-9bb0-4d62-90b4-8ceeb0a131b6", target_kind="sandbox", agent_run_group_id="grp_123", orchestration_role="coder", agent_runtime_profile_id="arp_123", provider="claude-code", cwd="/workspace", timeout=1800, prompt="Build the dashboard, run tests, and keep the preview live.", metadata={"artifact_paths": ["/workspace/report.html"]}, ) print(run["id"], run["status"], run.get("output", "")) artifacts = client.agent_runs.artifacts(run["id"]) report = client.agent_runs.download_artifact(run["id"], artifacts[0]["id"]) ``` ## CLI Use `miosa agent run` when the product or operator is choosing a target family: ```bash miosa agent run "Build the dashboard, run tests, and keep the preview live." \ --sandbox 41026070-9bb0-4d62-90b4-8ceeb0a131b6 \ --agent-profile arp_123 \ --provider claude-code \ --cwd /workspace \ --timeout 1800 \ --json miosa agent run "Open the CRM and verify the lead import" \ --computer desktop-prod-1 \ --provider osa \ --json miosa agent run "Audit the local browser workflow" \ --host host_abc123 \ --agent-profile arp_123 \ --json miosa runs files --json miosa runs download-file --output ./report.html ``` Use `miosa sandbox prompt` when you already know the target is a sandbox: ```bash miosa sandbox prompt 41026070-9bb0-4d62-90b4-8ceeb0a131b6 \ --provider claude-code \ --cwd /workspace \ --timeout 1800 \ --json \ -- "Build the dashboard, run tests, and keep the preview live." ``` Use the API-level `command` field with `provider: "custom"` when you bring your own runtime harness. The CLI exposes the built-in providers first. ## Product guidance Render the run status, provider, target, prompt, output, and request id in your task stream so users can see what the agent did. The agent should write files and run commands inside the sandbox, not build locally and upload a finished result afterward. Store your workspace, user, project, artifact, or task ids in metadata so you can connect MIOSA runs to your product records. Store your own dispatch id in `metadata` when sending work from queues. The queryable run store will use the same response shape when retry-safe history lands. ## See also - [Agent orchestration](/docs/guides/agent-orchestration/) - [Backend orchestration](/docs/integrations/backend-orchestration/) - [Sandboxes API](/docs/api-reference/sandboxes/) - [Computers API](/docs/api-reference/computers/) - [CLI](/docs/cli/) --- # Agent Runtime Profiles URL: https://miosa.ai/docs/api-reference/agent-runtime-profiles Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/agent-runtime-profiles Source: src/routes/docs/api-reference/agent-runtime-profiles/+page.md Description: Configure reusable agent runtime defaults for sandboxes, computers, and custom agent sessions. # Agent Runtime Profiles Agent Runtime Profiles define how MIOSA should prepare an agent runtime for a tenant, workspace, or project. Use profiles to standardize OSA, Codex, Claude Code, Pi, Hermes, or custom runtimes across sandboxes and computers without passing the same model, tools, connectors, policy, and environment on every run. Base path: `/api/v1/agent-runtime-profiles` Profiles configure runtime shape. Agent Runs still provide the prompt, target, timeout, and task-specific metadata. ## Endpoints | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/agent-runtime-profiles` | List profiles for the tenant | | `POST` | `/api/v1/agent-runtime-profiles` | Create a profile | | `GET` | `/api/v1/agent-runtime-profiles/{id}` | Fetch one profile | | `PUT` | `/api/v1/agent-runtime-profiles/{id}` | Update a profile | | `DELETE` | `/api/v1/agent-runtime-profiles/{id}` | Delete a profile | List can be filtered with `workspace_id` or `project_id`. ## Create ```http POST /api/v1/agent-runtime-profiles Authorization: Bearer msk_u_... Content-Type: application/json ``` ```json { "name": "clinic-iq-claude", "runtime": "claude-code", "workspace_id": "ws_123", "project_id": "proj_123", "description": "Default ClinicIQ coding and browser QA runtime", "applies_to": { "resources": ["sandbox", "computer"] }, "tools": ["filesystem", "shell", "browser", "artifacts"], "connectors": ["anthropic/clinic-iq", "refero/managed"], "env": { "ANTHROPIC_MODEL": "claude-sonnet-4.6" }, "policy": { "approval_required_for": ["deploy", "external_write"], "max_child_runs": 25 }, "metadata": { "owner": "platform" }, "is_default": true } ``` ## Fields | Field | Description | |---|---| | `name` | Human-readable profile name. Unique within the relevant tenant/workspace/project scope. | | `runtime` | One of `osa`, `codex`, `claude`, `claude-code`, `pi`, `hermes`, or `custom`. | | `workspace_id` | Optional workspace scope. | | `project_id` | Optional project scope. | | `description` | Operator-facing explanation. | | `applies_to` | Resource selector, commonly `{ "resources": ["sandbox", "computer"] }`. | | `tools` | Tool ids the runtime should expect, such as `filesystem`, `shell`, `browser`, `artifacts`, or `deployments`. | | `connectors` | Connector UIDs to bind when the target runtime is created. | | `env` | Non-secret runtime defaults. Put provider keys in runtime env/secrets, not here. | | `policy` | Product policy such as approvals, max child runs, or external-write rules. | | `metadata` | Product-specific labels and ids. | | `is_default` | Whether MIOSA should resolve this profile by default for the scope and resource family. | ## Resolution Agent Runs can pass `agent_runtime_profile_id` explicitly. If they do not, MIOSA resolves defaults by scope and target family: ```text explicit run profile -> project default -> workspace default -> tenant default -> no profile ``` The profile can be applied when creating or running sandboxes, computers, and OpenComputer/BYOC agent sessions. Request-level `env` values override non-secret profile env values for that single run. ## CLI ```bash miosa agent-runtime-profiles list --workspace ws_123 --json miosa agent-runtime-profiles create \ --name clinic-iq-claude \ --runtime claude-code \ --workspace ws_123 \ --project proj_123 \ --applies-to sandbox,computer \ --tools filesystem,shell,browser,artifacts \ --connectors anthropic/clinic-iq,refero/managed \ --env '{"ANTHROPIC_MODEL":"claude-sonnet-4.6"}' \ --policy '{"approval_required_for":["deploy","external_write"]}' \ --default \ --json miosa agent-runtime-profiles update arp_123 --runtime codex --json miosa agent-runtime-profiles delete arp_123 ``` ## Next Store inherited environment variables for tenant, workspace, project, and target scopes. Dispatch prompts into a sandbox, computer, or custom runtime. Use runtime profiles with groups, events, artifacts, and multi-agent fanout. --- # Audit Log API URL: https://miosa.ai/docs/api-reference/audit-log Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/audit-log Source: src/routes/docs/api-reference/audit-log/+page.md Description: Retrieve a cursor-paginated audit log of all actions taken within your tenant. The audit log records all significant actions performed within your tenant - resource creation and deletion, API key usage, settings changes, and more. Results are cursor-paginated and ordered newest-first. Base path: `/api/v1/audit-log` Audit events are immutable. There is no write API. The log is append-only and retained for 90 days on Pro plans, 365 days on Enterprise. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/audit-log` | Fetch recent audit events | --- ## List Audit Events **`GET /api/v1/audit-log`** ### Auth ``` Authorization: Bearer msk_... ``` ### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `limit` | integer | No | `50` | Events per page (max `100`) | | `after` | string | No | - | Cursor from previous response's `next_cursor` | | `type` | string | No | - | Filter by event type prefix (e.g. `sandbox.`) | | `actor_id` | string | No | - | Filter by actor user ID | | `resource_type` | string | No | - | Filter by resource type (e.g. `sandbox`) | | `resource_id` | string | No | - | Filter by resource ID | ### Response - `200 OK` ```json { "data": [ { "id": "evt_01hwxyz...", "type": "sandbox.created", "actor": { "type": "api_key", "id": "user_clinic_42" }, "resource": { "type": "sandbox", "id": "sbx_abc123" }, "ts": "2026-05-26T10:14:23Z", "metadata": { "image": "debian-12-sandbox-v8", "workspace_id": "ws_01hwxyz..." } } ], "next_cursor": "MjAyNi0wNS0yNlQxMDoxNDoyM1o" } ``` | Field | Type | Description | |-------|------|-------------| | `data[].id` | string | Unique event ID | | `data[].type` | string | Dot-namespaced event type | | `data[].actor.type` | string | `"user"` or `"api_key"` | | `data[].actor.id` | string | User ID or `external_user_id` for API key actors | | `data[].resource.type` | string | Resource type (e.g. `sandbox`, `computer`, `webhook`) | | `data[].resource.id` | string | Resource ID | | `data[].ts` | string | ISO 8601 timestamp | | `data[].metadata` | object | Event-specific detail fields | | `next_cursor` | string | Opaque cursor; omitted when no more pages | ### Common Event Types | Type | Trigger | |------|---------| | `sandbox.created` | Sandbox provisioned | | `sandbox.destroyed` | Sandbox deleted | | `sandbox.exec` | `POST /exec` called | | `computer.created` | Computer provisioned | | `computer.stopped` | Computer stopped | | `computer.deleted` | Computer deleted | | `snapshot.created` | Snapshot initiated | | `api_key.created` | API key minted | | `api_key.deleted` | API key revoked | | `tenant.preview_domain.set` | Preview domain configured | | `webhook.created` | Outgoing webhook created | | `quota.upserted` | Per-user quota set | | `settings.updated` | Tenant settings changed | Use the `type` filter with a prefix to narrow events (e.g. `type=sandbox.` returns all sandbox events). --- ## Pagination Audit events are returned newest-first. To page forward: 1. Issue the initial request without `after`. 2. If `next_cursor` is present, re-issue with `after={next_cursor}`. 3. Stop when `next_cursor` is absent. Cursors are opaque base64url strings encoding a timestamp. Do not parse or construct them. --- ## Errors | Status | Code | Cause | |--------|------|-------| | 400 | - | Invalid cursor format | --- ## Examples ```bash # First page curl "https://api.miosa.ai/api/v1/audit-log?limit=50&type=sandbox." \ -H "Authorization: Bearer msk_u_..." # Next page curl "https://api.miosa.ai/api/v1/audit-log?limit=50&after=MjAyNi0wNS0yNlQxMDoxNDoyM1o" \ -H "Authorization: Bearer msk_u_..." # Filter by resource curl "https://api.miosa.ai/api/v1/audit-log?resource_type=computer&resource_id=cmp_xyz789" \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Page through all sandbox events in the last page cursor = None while True: page = client.audit_log.list( type="sandbox.", limit=100, after=cursor, ) for event in page.data: print(event.type, event.resource.id, event.ts) if not page.next_cursor: break cursor = page.next_cursor ``` ```typescript const client = new Miosa(); let cursor: string | undefined; do { const page = await client.auditLog.list({ type: 'sandbox.', limit: 100, after: cursor, }); for (const event of page.data) { console.log(event.type, event.resource.id, event.ts); } cursor = page.nextCursor; } while (cursor); ``` --- # Cloud and BYOC API URL: https://miosa.ai/docs/api-reference/cloud Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/cloud Source: src/routes/docs/api-reference/cloud/+page.md Description: Organization-scoped AWS and GCP accounts, regions, pools, nodes, preflight, and host provisioning. Base path: `/api/v1/cloud` All resources belong to the active organization, which is returned as both `organization_id` and the compatibility field `tenant_id`. Cloud reads require `cloud:read` and mutations require `cloud:write`. Organization members can read inventory. Organization owners and admins can mutate it. Workspace-scoped credentials cannot mutate organization cloud capacity. AWS and GCP BYOC are gated private previews. Provider connection and preflight do not make workload placement selectable. Each exact organization, region, provider, and workload combination needs a current verification receipt from a real accepted host. ## Accounts - `GET /cloud/accounts` lists organization cloud accounts. - `POST /cloud/accounts` creates an AWS or GCP customer BYOC trust boundary. - `POST /cloud/accounts/{id}/aws-role` attaches an AWS AssumeRole ARN and generated external ID. - `POST /cloud/accounts/{id}/gcp-workload-identity` attaches the GCP project, customer actuator service account, and separate least-privilege worker service account without storing a service-account key. ## Regions - `GET /cloud/regions?cloud_account_id={id}` lists configured regions. - `POST /cloud/regions` records provider region, zone, network, host image, and exact immutable artifact configuration. - `PATCH /cloud/regions/{id}` updates that configuration and clears stale preflight readiness. Every launchable region must provide an HTTPS manifest URI, lowercase SHA-256 digest, version bound into the URI, and positive artifact generation. ## Pools - `GET /cloud/pools?cloud_region_id={id}` lists desired host pools. - `POST /cloud/pools` creates a pool configuration. - `PATCH /cloud/pools/{id}` updates organization capacity and placement guardrails. - `POST /cloud/pools/{pool_id}/provision` explicitly launches between 1 and 50 hosts. ```json { "count": 1 } ``` Provisioning returns one result per requested host. A successful launch starts in `provider_pending` and is not schedulable until host acceptance passes. ## Nodes - `GET /cloud/nodes?cloud_pool_id={id}` lists provider and acceptance state for a pool. Important node states include `provider_pending`, `acceptance_running`, `active`, `draining`, `terminated`, and `failed`. Only `active` nodes with a durable organization cell and accepted host session are eligible for placement. ## Preflight - `GET /cloud/preflights` lists account and region preflight evidence. - `POST /cloud/preflights` runs server-side account or region verification and records immutable evidence. Preflight and host acceptance are different gates. Preflight validates AWS or GCP identity, real provider API access, zone and machine availability, configured network and image references, and the immutable artifact contract before launch. Host acceptance validates the actual launched machine and promoted MIOSA runtime before placement. Sandbox, Computer, and Deployment placement are certified separately because one working runtime does not prove another workload contract. ## SDK ```typescript const result = await miosa.cloud.provisionPool(poolId, 1); const nodes = await miosa.cloud.listNodes({ cloudPoolId: poolId }); ``` ```python result = client.cloud.provision_pool(pool_id, count=1) nodes = client.cloud.list_nodes(cloud_pool_id=pool_id) ``` Sandbox creation does not take a pool ID. An active BYOC organization is automatically and exclusively placed into its configured scheduling cell. --- # 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 with a Linux desktop, streamed desktop access, and APIs for exec, files, apps, metrics, and desktop control. 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 limits vary by key, tenant policy, and endpoint family. Honor `X-RateLimit-*` and `Retry-After` response headers. 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. | | `agent_runtime_profile_id` | UUID/string | No | Force a specific agent runtime profile for this computer. Alias: `agent_profile_id`. | | `skip_agent_runtime_profile` | boolean | No | If `true`, do not apply the tenant/workspace default runtime profile. | If a default agent runtime profile exists for the workspace or tenant and it applies to computers, MIOSA applies it automatically. Profile env vars are stored in the encrypted computer env store and synced into the VM after OSA is healthy. Profile connectors are bound through MIOSA egress as opaque `miosa-tok-*` placeholder tokens. ### 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.computer.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" } ``` `sandbox_url` is a legacy preview field on the Computer response. Use `desktop_url` for the desktop stream and the `/computers/{id}/urls` endpoint for short-lived desktop and terminal URLs. ### Errors | Status | Error | Cause | |--------|-------|-------| | 400 | `name is required` | Missing name or template_type | | 402 | `INSUFFICIENT_CREDITS` | Not enough credits to create | | 404 | `AGENT_RUNTIME_PROFILE_NOT_FOUND` | Explicit profile ID does not exist in this tenant | | 422 | `TENANT_RESOLUTION_FAILED` | Cannot determine tenant for user | | 422 | `AGENT_RUNTIME_PROFILE_NOT_APPLICABLE` | Explicit profile does not apply to computers | ```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", "agent_runtime_profile_id": "9f4b3d8e-0000-0000-0000-000000000000" }' ``` --- ## 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 Desktop Stream Credentials **`GET /api/v1/computers/{id}/vnc-credentials`** Returns credentials for direct desktop streaming access. ### Response - `200 OK` ```json { "desktop_url": "https://my-computer.computer.miosa.ai/desktop/index.html", "ws_url": "wss://my-computer.computer.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 desktop 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.computer.miosa.ai/desktop/index.html", "stream_url": "https://my-computer.computer.miosa.ai/desktop/index.html", "terminal_url": "wss://my-computer.computer.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. ### Response - `200 OK` Response 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` | Computer command service unavailable | --- ## 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 | | `POST` | `/deployments/{id}/domains/{domain_id}/verify` | Trigger DNS verification | | `DELETE` | `/deployments/{id}/domains/{domain_id}` | Remove a domain | ### 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 MIOSA-managed fallback. ```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 fallback URL for that resource. 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`); } } ``` --- # Databases API URL: https://miosa.ai/docs/api-reference/databases Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/databases Source: src/routes/docs/api-reference/databases/+page.md Description: Provision and operate managed databases, backups, restores, branches, replicas, credentials, and placement. Managed databases have a lifecycle independent from sandboxes and deployment runtime instances. Deleting or replacing compute does not implicitly delete an attached database. Base path: `/api/v1/databases` ## Core lifecycle | Method | Path | Purpose | |---|---|---| | `GET` | `/databases` | List databases visible to the current tenant and workspace scope | | `POST` | `/databases` | Create a managed database | | `GET` | `/databases/{id}` | Inspect one database | | `DELETE` | `/databases/{id}` | Permanently delete one database | | `POST` | `/databases/{id}/start` | Start a stopped or recoverable database | | `POST` | `/databases/{id}/stop` | Stop a running database while preserving its volume | | `POST` | `/databases/{id}/restart` | Restart the database runtime | | `POST` | `/databases/{id}/wake` | Wake a database that can be resumed | | `POST` | `/databases/{id}/resize-compute` | Change its compute allocation | | `POST` | `/databases/{id}/move` | Move the database through the managed placement workflow | ## Data topology and recovery | Method | Path | Purpose | |---|---|---| | `POST` | `/databases/{id}/branch` | Create an independent database branch | | `POST` | `/databases/{id}/replicas` | Create a replica | | `POST` | `/databases/{id}/backups` | Create an on-demand backup | | `GET` | `/databases/{id}/backups` | List backups | | `GET` | `/databases/{id}/backups/{backup_id}` | Inspect one backup | | `POST` | `/databases/{id}/restore` | Restore from a selected backup | | `GET` | `/databases/{id}/restores` | List restore operations | | `GET` | `/databases/{id}/credentials` | Return the current connection contract | Logs and metrics use the common observability endpoints: ```http GET /api/v1/databases/{id}/logs GET /api/v1/databases/{id}/logs/stream GET /api/v1/databases/{id}/metrics ``` ## Create ```bash curl -X POST https://api.miosa.ai/api/v1/databases \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"app-production","engine":"postgresql"}' ``` Creation is asynchronous. Wait until the returned database reaches `running` before connecting. ```bash miosa databases create postgresql --name app-production --wait --json ``` ## Credentials ```bash curl https://api.miosa.ai/api/v1/databases/$DATABASE_ID/credentials \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Use the returned recommended connection URL. Do not construct hostnames, ports, usernames, or database names from resource metadata. ## Backup and restore ```bash miosa db backup --json miosa db restore --backup --force --json ``` Restore replaces the target database contents with the selected backup. Confirm the database ID and backup ID before approving the operation. Database restore does not change the active application release. Application rollback does not reverse database migrations. Use backward-compatible expand, migrate, and contract changes when releases must remain rollback-safe. ## Permissions | Action | Required scope | |---|---| | List, inspect, read metrics, or fetch credentials | `databases:read` | | Create, resize, branch, back up, restore, move, or delete | `databases:write` | ## See also Choose the correct managed data product and understand its lifecycle. Connect applications and manage schema changes safely. Bind separate databases to production, staging, and development. --- # 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. Use `POST /api/v1/sandboxes/:id/publish` or `miosa sandbox publish` to turn a tested sandbox into an immutable release. Pass an existing deployment ID when updating so its identity and URL remain stable. ## Endpoints ```http POST /api/v1/projects/:project_id/deployments 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 POST /api/v1/deployments/:id/rollback POST /api/v1/sandboxes/:id/publish POST /api/v1/sandboxes/:id/deploy # Compatibility bridge ``` 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 - [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 computer command service 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 limits vary by key, tenant policy, and endpoint family. Honor `X-RateLimit-*` and `Retry-After` response headers. 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 computer command service | --- ## 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 --- # API Reference / App Engine URL: https://miosa.ai/docs/api-reference/docker-deploy Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/docker-deploy Source: src/routes/docs/api-reference/docker-deploy/+page.md Description: REST API for App Engine templates, workspace hosts, and sandbox-to-container publishing. App Engine is the dedicated workspace host for customers who need many containerized apps on one always-on MIOSA-managed runtime. Use the regular Deployments API for the deployment object. Use the App Engine API to choose templates and provision the workspace host. App Engine apps still return normal MIOSA deployment URLs and custom-domain records. The workspace host runs the app containers while MIOSA owns DNS, TLS, routing, health checks, and deployment records. ## Endpoints ```http GET /api/v1/docker-deploy/templates GET /api/v1/docker-deploy/templates/:id GET /api/v1/docker-deploy/hosts?workspace_id=:workspace_id POST /api/v1/docker-deploy/hosts/ensure GET /api/v1/docker-deploy/hosts/:id POST /api/v1/sandboxes/:id/publish POST /api/v1/deployments/:id/publish ``` All endpoints require `Authorization: Bearer `. Mutations should include an `Idempotency-Key` header. ## List templates ```http GET /api/v1/docker-deploy/templates ``` Returns curated container deployment templates such as Next.js app, Next.js static site, React/Vite, static HTML, Python API, Node API, and compose-backed app patterns. Response: ```json { "data": [ { "id": "nextjs-app", "name": "Next.js App", "runtime": "node", "build_command": "npm install && npm run build", "run_command": "npm start", "port": 3000, "health_check_path": "/" } ] } ``` ## Get template ```http GET /api/v1/docker-deploy/templates/:id ``` Use this when your UI needs the template defaults before creating or publishing a deployment. ## List workspace hosts ```http GET /api/v1/docker-deploy/hosts?workspace_id=550e8400-e29b-41d4-a716-446655440000 ``` Returns App Engine hosts visible to the caller. Response: ```json { "data": [ { "id": "78745c76-d5e6-4541-b651-1018f7cc0d89", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "computer_id": "cmp_...", "status": "active", "runtime_status": "healthy", "runtime_version": "2026.07" } ] } ``` ## Ensure workspace host ```http POST /api/v1/docker-deploy/hosts/ensure ``` Creates or returns the dedicated App Engine host for a workspace. One host can run many deployments for that workspace. Body: ```json { "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "region": "us-central", "size": "standard", "metadata": { "customer": "Clinic IQ" } } ``` Response: ```json { "data": { "id": "78745c76-d5e6-4541-b651-1018f7cc0d89", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "computer_id": "cmp_...", "status": "provisioning", "runtime_status": "booting" } } ``` ## Get host ```http GET /api/v1/docker-deploy/hosts/:id ``` Returns the latest status for one App Engine host. A deployment is not ready until the host is active and the deployed app health check passes through that host. ## Publish from sandbox ```http POST /api/v1/sandboxes/:id/publish ``` Body: ```json { "name": "Dr Smith Lead Magnet", "deployment_type": "docker_deploy", "docker_deploy_template_id": "nextjs-app", "workspace_id": "550e8400-e29b-41d4-a716-446655440000", "project_id": "660e8400-e29b-41d4-a716-446655440001", "build_command": "npm install && npm run build", "run_command": "npm start", "port": 3000, "health_check_path": "/", "external_workspace_id": "clinic_123", "external_project_id": "lead_magnet_456" } ``` MIOSA packages the sandbox, ensures the workspace App Engine host exists, installs the release, waits for the app route health check, then returns the deployment and public URL. ## Publish existing deployment ```http POST /api/v1/deployments/:id/publish ``` Use the same App Engine fields when publishing a new version of an existing deployment: ```json { "source_sandbox_id": "sbx_...", "deployment_type": "docker_deploy", "docker_deploy_template_id": "nextjs-app", "build_command": "npm install && npm run build", "run_command": "npm start", "port": 3000, "health_check_path": "/" } ``` ## See also - [App Engine guide](/docs/deploy/docker-deploy/) - end-to-end setup and operating model - [Deployments](/docs/api-reference/deployments/) — deployment object, publish, rollback, delete - [Custom Domains](/docs/api-reference/custom-domains/) — attach exact domains to deployments - [Workspaces](/docs/api-reference/workspaces/) — workspace ownership and attribution --- # Egress (Security) API URL: https://miosa.ai/docs/api-reference/egress Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/egress Source: src/routes/docs/api-reference/egress/+page.md Description: API reference for the MIOSA Egress credential vault, network allowlist, OAuth Connect, and audit log. The Egress API powers the **Security** tab on every Computer and Sandbox: encrypted credential vault, OAuth Connect, network allowlist with audit-only / enforce modes, and a live-tail audit log. Base path: `/api/v1/egress` All endpoints require `Authorization: Bearer <msk_*>`. See [Authentication](/docs/authentication). For higher-level access, use the [SDKs](/docs/sdks/egress). For the architecture overview, walkthroughs, and white-label patterns, see the [Security guides](/docs/security). --- ## Secrets ### Create a secret **`POST /api/v1/egress/secrets`** Stores an encrypted credential and optionally binds it to a resource as an environment variable. The raw value is encrypted with ChaCha20-Poly1305 and never returned again. ```bash curl -X POST https://api.miosa.ai/api/v1/egress/secrets \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "openai_key", "value": "sk-proj-...", "type": "api_key", "scope": "user", "expose_as_env": "OPENAI_API_KEY" }' ``` Required body fields: `name`, `value`, `type`, `scope`. | Field | Values | Notes | |---|---|---| | `type` | `api_key`, `oauth_connect`, `oauth_machine` | OAuth flows are usually initiated via `/oauth/start` instead. | | `scope` | `tenant`, `workspace`, `user`, `external_user`, `external_workspace` | The 5-tier scoping model. | | `expose_as_env` | shell-safe upper-snake-case | If set, auto-creates a binding. | Optional scoping fields based on `scope`: `workspace_id`, `owner_user_id`, `external_user_id`, `external_workspace_id`. Returns `201 Created` with `{data: , binding: }`. The `data.preview` is the first-6 + last-4 of the value; the full value is never returned. ### List secrets **`GET /api/v1/egress/secrets`** Query params: `scope`, `workspace_id`, `owner_user_id`, `external_user_id`, `external_workspace_id`. Returns metadata only (no values). ### Get a secret **`GET /api/v1/egress/secrets/:id`** - metadata for one secret. ### Rotate **`PATCH /api/v1/egress/secrets/:id`** Re-encrypts with a new value. The bound `placeholder_token` does not change; every sandbox using it picks up the new value on its next outbound request. ```json { "value": "sk-proj-new-value", "expires_at": "2026-12-31T00:00:00Z" } ``` ### Delete **`DELETE /api/v1/egress/secrets/:id`** --- ## Bindings A binding links a secret to a Computer or Sandbox and surfaces it as an environment variable (or opaque header token). ### Create a binding **`POST /api/v1/egress/bindings`** ```json { "secret_id": "", "resource_id": "", "resource_type": "sandbox", "exposure": "env_var", "expose_as_env": "OPENAI_API_KEY" } ``` Returns the binding including its `placeholder_token` - the opaque `miosa-tok-…` value the workload sees in env. ### List + delete - `GET /api/v1/egress/bindings?resource_id=&resource_type=sandbox` - `DELETE /api/v1/egress/bindings/:id` --- ## OAuth Connect ### List provider catalog **`GET /api/v1/egress/oauth/providers`** - returns providers visible to the calling tenant (platform defaults merged with tenant overrides). No client secrets exposed. ### Start an OAuth flow **`POST /api/v1/egress/oauth/start`** ```json { "provider": "github", "expose_as_env": "GITHUB_TOKEN", "scope": "user", "owner_user_id": "" } ``` Returns `{authorize_url, state}`. Open `authorize_url` in a browser; the user approves; the upstream redirects to `/api/v1/egress/oauth/callback?code=&state=`. On success the tokens are encrypted into a new `oauth_connect` secret + binding. ### Status **`GET /api/v1/egress/oauth/status?state=`** - poll while the user is completing the flow. Returns `pending`, `complete`, or `failed`. ### Admin: tenant-override providers (white-label) - `POST /api/v1/egress/oauth/admin/providers` - register your own OAuth app credentials so end-users see your brand on the consent screen. - `DELETE /api/v1/egress/oauth/admin/providers/:id` Requires admin (`msk_a_*`) credentials. --- ## Network policies + allowlist ### Policies - `GET /api/v1/egress/policies` - list (optionally filter by `resource_id`) - `POST /api/v1/egress/policies` - create - `GET /api/v1/egress/policies/:id` - `PATCH /api/v1/egress/policies/:id` - set `mode` (`audit_only` / `enforce`), `default_action` (`allow` / `deny`) Each Computer or Sandbox resolves the policy in this order: resource override → tenant default. Tenant default starts in `audit_only` mode. ### Allowlist rules - `GET /api/v1/egress/allowlist?policy_id=` - `POST /api/v1/egress/allowlist` - `DELETE /api/v1/egress/allowlist/:id` ```json { "policy_id": "", "pattern": "*.openai.com", "kind": "wildcard", "method": "POST", "path_glob": "/v1/*", "action": "allow", "warn_only": false, "priority": 100 } ``` Pattern kinds: `exact` (literal host), `wildcard` (`*.example.com`), `cidr` (IP literal targets only). --- ## Audit log ### Query **`GET /api/v1/egress/audit`** Query params: `resource_id`, `host`, `action` (`allow`/`reject`/`stub`/`error`), `since` (relative like `1h` / `7d` or ISO timestamp), `until`, `limit` (default 100, max 1000), `external_user_id`, `external_workspace_id`. Each row includes the full identity chain (`tenant_id`, `workspace_id`, `owner_user_id`, `external_user_id`, `external_workspace_id`), request metadata (`host`, `method`, `path`, `sni`, `mode`, `remote_addr`), outcome (`action`, `rejected_by`, `status_code`, `duration_ms`, `bytes_in`, `bytes_out`, `error_message`), and the transform trace JSONB (`request_transforms`, `response_transforms`, `mcp`). ### Get a single event **`GET /api/v1/egress/audit/:id`** ### Allowlist suggestions **`GET /api/v1/egress/audit/suggestions?resource_id=`** - returns the hosts a resource has contacted recently, ranked by frequency. The basis for the one-click "Lock down with selected as allowlist" UX. ### Live tail (WebSocket) **`wss://api.miosa.ai/api/v1/egress/audit/resource/:resource_id?token=`** Subprotocol: `miosa-egress-audit-v1`. Pushes `{"type":"audit_event","data":{...}}` frames in real time as outbound requests are recorded. Tenant-scoped variant (admin only): `wss://api.miosa.ai/api/v1/egress/audit/tenant/:tenant_id?token=`. --- ## Rate limits + retention | Scope | Limit | |---|---| | All `/egress/*` endpoints | Current limit is returned through `X-RateLimit-*` headers | | Audit retention | 90 days for every tenant; longer on enterprise | | Audit live-tail (WebSocket) | unlimited; does not count against REST quota | --- ## Errors Same envelope as the rest of the API - `{"error": "...", "details": ...}` with the appropriate status code. | Status | When | |---|---| | `400` | invalid body, unknown scope, malformed env var name | | `401` | missing / invalid credential | | `403` | role does not allow the operation (e.g., creating tenant-scoped secret as a non-admin) | | `404` | secret / binding / policy / event not found in this tenant | | `409` | secret name collision within scope (unique partial index) | | `422` | scope ↔ identity column mismatch (e.g., scope=user without owner_user_id) | --- ## SDK shortcut All endpoints above are wrapped by the [Egress SDK reference](/docs/sdks/egress) with idiomatic methods in Python, TypeScript, Go, Java, and Elixir. --- # 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. Limits vary by key family, tenant policy, and endpoint family. Honor `X-RateLimit-*` and `Retry-After` instead of hardcoding a client-side limit. --- ## 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 computer command service 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 | computer command service 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 Direct snapshot create, list, restore, and delete operations apply to desktop Computers under `/api/v1/computers/{id}/snapshots`. Canonical sandbox V1 exposes snapshot-backed fork at `/api/v1/sandboxes/{id}/fork`, not customer-managed snapshot IDs. | 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 computer 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 limits vary by key, tenant policy, and endpoint family. Honor `X-RateLimit-*` and `Retry-After` response headers. 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 /workspace", "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 /workspace && 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(\"/workspace\"):\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 `/workspace`, `/home`, `/root`, `/tmp`, `/opt`, and `/srv`. Base path: `/api/v1/computers/{id}/files` The computer must be in `"running"` status. All operations go through the computer command service. --- ## 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: `/workspace/` | ### Response - `201 Created` ```json { "success": true, "file": { "path": "/workspace/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=/workspace/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: `/workspace` | ### 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": "/workspace" } ``` ### 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=/workspace" \ -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=/workspace/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": "/workspace/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": "/workspace/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": "/workspace/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": "/workspace/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 `/workspace`, `/home`, `/root`, `/tmp`, `/opt`, or `/srv` 3. **Rejection** - Paths outside these prefixes return `403 FORBIDDEN_PATH` This prevents path traversal attacks. For example, `/workspace/../../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 computer command service and restricted to allowed paths. Base path: `/api/v1/computers/{id}/files` **Allowed paths:** `/workspace`, `/home`, `/root`, `/tmp`, `/opt`, `/srv` 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: '/workspace/hello.py', content: 'print("Hello from MIOSA")', }); // List the directory const { files } = await client.files.list(computerId, '/workspace'); // Download it back const content = await client.files.download(computerId, '/workspace/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: `/workspace/` | ### Response - `201 Created` ```json { "success": true, "file": { "path": "/workspace/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=/workspace/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": "/workspace/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": "/workspace/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: `/workspace` | ### 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": "/workspace" } ``` ```bash curl "https://api.miosa.ai/api/v1/computers/{id}/files?path=/workspace" \ -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=/workspace/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": "/workspace/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": "/workspace/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": "/workspace/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": "/workspace/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": "/workspace/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=/workspace/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": "/workspace/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": "/workspace/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": "/workspace/old.txt", "destination": "/workspace/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": "/workspace/app.py", "destination": "/workspace/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": "/workspace/template.py", "destination": "/workspace/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": "/workspace/template.py", "destination": "/workspace/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": "/workspace/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": "/workspace/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 `/workspace`, `/home`, `/root`, `/tmp`, `/opt`, or `/srv` 3. **Rejection** - Paths outside these prefixes return `403 FORBIDDEN_PATH` `/workspace/../../etc/passwd` expands to `/etc/passwd` and is rejected. --- ## Common Recipes ### Scaffold a project directory ```typescript // Create structure await client.files.mkdir(computerId, { path: '/workspace/app/src', recursive: true }); await client.files.mkdir(computerId, { path: '/workspace/app/tests', recursive: true }); // Write files await client.files.write(computerId, { path: '/workspace/app/src/main.py', content: mainPy }); await client.files.write(computerId, { path: '/workspace/app/requirements.txt', content: reqs }); // Make entrypoint executable await client.files.chmod(computerId, { path: '/workspace/app/src/main.py', mode: '755' }); ``` ### Retrieve build artifacts ```python # List the build output directory files = client.files.list(computer_id, path="/workspace/app/dist") for f in files["files"]: if f["type"] == "file": content = client.files.export(computer_id, path=f"/workspace/app/dist/{f['name']}") save_artifact(f["name"], content["content_base64"]) ``` --- # Invites API URL: https://miosa.ai/docs/api-reference/invites Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/invites Source: src/routes/docs/api-reference/invites/+page.md Description: API reference for the workspace invite and org invite flows - send, list, revoke, preview, and accept email invitations. MIOSA has two parallel invite flows: - **Workspace invites** - invite a user to a specific workspace. If the email already belongs to an org member, the user is added directly (no email sent). If not, an invite email is dispatched and accepting it creates both the `tenant_members` and `workspace_members` rows atomically. - **Org invites** - invite a user to join the org (tenant) without pre-selecting a workspace. The accept step only creates the `tenant_members` row. Both flows produce opaque tokens. The `GET /invites/:token` and `GET /workspace-invites/:token` preview endpoints are **public** - they can be called without an API key to render a landing page before the user signs in. Email invite links should point at the MIOSA frontend, not the API host. Use `https://miosa.ai/invites/{token}` for org invites and `https://miosa.ai/workspace-invites/{token}` for workspace invites. Legacy `https://miosa.ai/invite/{token}` links redirect to the org invite landing page for compatibility. --- ## Workspace Invite Endpoints | Method | Path | Auth | Description | |--------|------|------|-------------| | `POST` | `/workspaces/{id}/invites` | Required | Send a workspace invite | | `GET` | `/workspaces/{id}/invites` | Required | List pending invites | | `DELETE` | `/workspaces/{id}/invites/{invite_id}` | Required | Revoke an invite | | `GET` | `/workspace-invites/{token}` | None (public) | Preview invite details | | `POST` | `/workspace-invites/{token}/accept` | Required | Accept the invite | ## Org Invite Endpoints | Method | Path | Auth | Description | |--------|------|------|-------------| | `POST` | `/tenants/{id}/invites` | Required (admin/owner) | Send an org invite | | `GET` | `/tenants/{id}/invites` | Required (admin/owner) | List pending org invites | | `DELETE` | `/tenants/{id}/invites/{invite_id}` | Required (admin/owner) | Revoke an org invite | | `GET` | `/invites/{token}` | None (public) | Preview org invite details | | `POST` | `/invites/{token}/accept` | Required | Accept the org invite | --- ## Workspace Invite Flow ### Send Workspace Invite **`POST /api/v1/workspaces/{id}/invites`** Sends an invite email and returns the invite record. If the `email` already belongs to a tenant member the user is added directly (response `type = "added"`, no email sent). #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | Yes | Email address to invite | | `role` | string | Yes | `owner`, `admin`, `member`, or `viewer` | #### Response - `201 Created` (new invite) ```json { "type": "invited", "data": { "id": "inv_abc123", "workspace_id": "ws-uuid", "tenant_id": "ten-uuid", "email": "alice@example.com", "role": "member", "invited_by": "usr_owner", "expires_at": "2026-05-29T09:00:00Z", "accepted_at": null, "inserted_at": "2026-05-22T09:00:00Z" } } ``` #### Response - `200 OK` (existing member added directly) ```json { "type": "added", "data": { "user_id": "usr_def456", "workspace_id": "ws-uuid", "role": "member", "joined_at": "2026-05-22T09:00:00Z", "added_by": "usr_owner" } } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/workspaces/{id}/invites \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com", "role": "member"}' ``` ```typescript const client = new Miosa(); const result = await client.workspaceInvites.create('ws-uuid', { email: 'alice@example.com', role: 'member', }); // result.type === 'invited' | 'added' ``` ```python client = miosa.Miosa() result = client.workspace_invites.create("ws-uuid", "alice@example.com", "member") print(result["type"]) # "invited" or "added" ``` ```go result, err := client.WorkspaceInvites.Create(ctx, "ws-uuid", miosa.CreateWorkspaceInviteInput{ Email: "alice@example.com", Role: "member", }) ``` ```elixir {:ok, result} = Miosa.WorkspaceInvites.create(client, "ws-uuid", "alice@example.com", "member") IO.inspect(result["type"]) ``` ```java Map result = miosa.workspaceInvites().create("ws-uuid", "alice@example.com", "member"); String type = (String) result.get("type"); // "invited" or "added" ``` --- ### List Workspace Invites **`GET /api/v1/workspaces/{id}/invites`** Returns all pending workspace invites (excludes accepted and revoked). #### Response - `200 OK` ```json { "data": [ { "id": "inv_abc123", "workspace_id": "ws-uuid", "tenant_id": "ten-uuid", "email": "alice@example.com", "role": "member", "invited_by": "usr_owner", "expires_at": "2026-05-29T09:00:00Z", "accepted_at": null, "inserted_at": "2026-05-22T09:00:00Z" } ] } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/invites \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript const invites = await client.workspaceInvites.list('ws-uuid'); ``` ```python invites = client.workspace_invites.list("ws-uuid") ``` ```go invites, err := client.WorkspaceInvites.List(ctx, "ws-uuid") ``` ```elixir {:ok, invites} = Miosa.WorkspaceInvites.list(client, "ws-uuid") ``` ```java List> invites = miosa.workspaceInvites().list("ws-uuid"); ``` --- ### Revoke Workspace Invite **`DELETE /api/v1/workspaces/{id}/invites/{invite_id}`** Revokes a pending invite. Returns `409` if the invite was already accepted. #### Response - `200 OK` ```json { "invite_id": "inv_abc123", "revoked": true } ``` ```bash curl -X DELETE https://api.miosa.ai/api/v1/workspaces/{id}/invites/{invite_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript await client.workspaceInvites.revoke('ws-uuid', 'inv_abc123'); ``` ```python client.workspace_invites.revoke("ws-uuid", "inv_abc123") ``` ```go _, err := client.WorkspaceInvites.Revoke(ctx, "ws-uuid", "inv_abc123") ``` ```elixir {:ok, _} = Miosa.WorkspaceInvites.revoke(client, "ws-uuid", "inv_abc123") ``` ```java miosa.workspaceInvites().revoke("ws-uuid", "inv_abc123"); ``` --- ### Preview Workspace Invite (public) **`GET /api/v1/workspace-invites/{token}`** Returns invite metadata without requiring authentication. Use this to render the pre-auth landing page. #### Response - `200 OK` ```json { "data": { "workspace_name": "Dr. Smith Clinic", "role": "member", "expires_at": "2026-05-29T09:00:00Z", "expired": false, "accepted": false } } ``` ```bash curl https://api.miosa.ai/api/v1/workspace-invites/{token} ``` ```typescript const preview = await client.workspaceInvites.preview(token); if (!preview.expired) { // show accept button } ``` ```python preview = client.workspace_invites.preview(token) if not preview["expired"]: pass # show accept button ``` ```go preview, err := client.WorkspaceInvites.Preview(ctx, token) ``` ```elixir {:ok, preview} = Miosa.WorkspaceInvites.preview(client, token) unless preview["expired"], do: ... ``` ```java Map preview = miosa.workspaceInvites().preview(token); ``` --- ### Accept Workspace Invite **`POST /api/v1/workspace-invites/{token}/accept`** Accepts the invite on behalf of the authenticated user. If the user is new to the org, a `tenant_members` row is created atomically alongside the `workspace_members` row. The caller's API key email must match the invite email (case-insensitive). #### Response - `200 OK` ```json { "accepted": true, "workspace_id": "ws-uuid", "tenant_id": "ten-uuid", "role": "member" } ``` #### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_TOKEN` | Token not found or expired | | 400 | `REVOKED` | Invite was revoked | | 422 | `EMAIL_MISMATCH` | Authenticated user's email differs from invite email | ```bash curl -X POST https://api.miosa.ai/api/v1/workspace-invites/{token}/accept \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript const result = await client.workspaceInvites.accept(token); console.log('joined workspace:', result.workspaceId); ``` ```python result = client.workspace_invites.accept(token) print("joined workspace:", result["workspace_id"]) ``` ```go result, err := client.WorkspaceInvites.Accept(ctx, token) ``` ```elixir {:ok, result} = Miosa.WorkspaceInvites.accept(client, token) IO.puts("joined workspace: #{result["workspace_id"]}") ``` ```java Map result = miosa.workspaceInvites().accept(token); ``` --- ## Org Invite Flow Org invites grant membership in the tenant (org) without assigning a specific workspace. Write operations require `admin` or `owner` role. ### Send Org Invite **`POST /api/v1/tenants/{id}/invites`** Dispatches an invite email. The response includes an `invite_url` that is host-aware - on white-label tenants it uses the tenant's custom domain. #### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `email` | string | Yes | Email address to invite | | `role` | string | Yes | `owner`, `admin`, or `member` | #### Response - `201 Created` ```json { "data": { "invite_id": "inv_xyz789", "email": "bob@example.com", "role": "member", "expires_at": "2026-05-29T09:00:00Z", "invite_url": "https://miosa.ai/invites/eyJ..." } } ``` ```bash curl -X POST https://api.miosa.ai/api/v1/tenants/{id}/invites \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "bob@example.com", "role": "member"}' ``` ```typescript const invite = await client.orgInvites.create(tenantId, { email: 'bob@example.com', role: 'member', }); console.log('invite URL:', invite.inviteUrl); ``` ```python invite = client.org_invites.create(tenant_id, "bob@example.com", "member") print("invite URL:", invite["invite_url"]) ``` ```go invite, err := client.OrgInvites.Create(ctx, tenantID, miosa.CreateOrgInviteInput{ Email: "bob@example.com", Role: "member", }) ``` ```elixir {:ok, invite} = Miosa.OrgInvites.create(client, tenant_id, "bob@example.com", "member") IO.puts("invite URL: #{invite["invite_url"]}") ``` ```java Map invite = miosa.orgInvites().create(tenantId, "bob@example.com", "member"); System.out.println("invite URL: " + invite.get("invite_url")); ``` --- ### List Org Invites **`GET /api/v1/tenants/{id}/invites`** Returns all pending org invites. ```bash curl https://api.miosa.ai/api/v1/tenants/{id}/invites \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript const invites = await client.orgInvites.list(tenantId); ``` ```python invites = client.org_invites.list(tenant_id) ``` ```go invites, err := client.OrgInvites.List(ctx, tenantID) ``` ```elixir {:ok, invites} = Miosa.OrgInvites.list(client, tenant_id) ``` ```java List> invites = miosa.orgInvites().list(tenantId); ``` --- ### Revoke Org Invite **`DELETE /api/v1/tenants/{id}/invites/{invite_id}`** Revokes a pending org invite. Returns `409` if the invite was already accepted. ```bash curl -X DELETE https://api.miosa.ai/api/v1/tenants/{id}/invites/{invite_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript await client.orgInvites.revoke(tenantId, inviteId); ``` ```python client.org_invites.revoke(tenant_id, invite_id) ``` ```go _, err := client.OrgInvites.Revoke(ctx, tenantID, inviteID) ``` ```elixir {:ok, _} = Miosa.OrgInvites.revoke(client, tenant_id, invite_id) ``` ```java miosa.orgInvites().revoke(tenantId, inviteId); ``` --- ### Preview Org Invite (public) **`GET /api/v1/invites/{token}`** Returns invite metadata without authentication. #### Response - `200 OK` ```json { "data": { "email": "bob@example.com", "tenant_name": "Acme Corp", "role": "member", "expires_at": "2026-05-29T09:00:00Z", "expired": false, "accepted": false } } ``` ```bash curl https://api.miosa.ai/api/v1/invites/{token} ``` ```typescript const preview = await client.orgInvites.preview(token); console.log('invited to org:', preview.tenantName); ``` ```python preview = client.org_invites.preview(token) print("invited to org:", preview["tenant_name"]) ``` ```go preview, err := client.OrgInvites.Preview(ctx, token) ``` ```elixir {:ok, preview} = Miosa.OrgInvites.preview(client, token) IO.inspect(preview["tenant_name"]) ``` ```java Map preview = miosa.orgInvites().preview(token); ``` --- ### Accept Org Invite **`POST /api/v1/invites/{token}/accept`** Accepts an org invite on behalf of the authenticated user. Creates a `tenant_members` row. The caller's API key email must match the invite email (case-insensitive). #### Response - `200 OK` ```json { "accepted": true, "tenant_id": "ten-uuid", "tenant": { "id": "ten-uuid", "name": "Acme Corp" } } ``` #### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `invalid_token` | Token not found or expired | | 422 | `EMAIL_MISMATCH` | Session email differs from invite email | ```bash curl -X POST https://api.miosa.ai/api/v1/invites/{token}/accept \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript const result = await client.orgInvites.accept(token); console.log('joined org:', result.tenantId); ``` ```python result = client.org_invites.accept(token) print("joined org:", result["tenant_id"]) ``` ```go result, err := client.OrgInvites.Accept(ctx, token) ``` ```elixir {:ok, result} = Miosa.OrgInvites.accept(client, token) IO.puts("joined org: #{result["tenant_id"]}") ``` ```java Map result = miosa.orgInvites().accept(token); ``` --- ## Common Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_TOKEN` / `invalid_token` | Token not found, expired, or revoked | | 400 | `REVOKED` | Invite was explicitly revoked | | 409 | `ALREADY_ACCEPTED` | Cannot revoke an invite that was already accepted | | 422 | `EMAIL_MISMATCH` | Authenticated user's email does not match the invite email | | 422 | `MISSING_EMAIL` | `email` field missing from request body | --- ## See also - [Workspace Members API](/docs/api-reference/members) - manage the roster directly (requires prior org membership) - [Workspaces API](/docs/api-reference/workspaces) - workspace CRUD - [Error Codes](/docs/api-reference/errors) - full error reference --- # Workspace Members API URL: https://miosa.ai/docs/api-reference/members Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/members Source: src/routes/docs/api-reference/members/+page.md Description: API reference for managing workspace member rosters - add, remove, and update roles for users inside a workspace. Workspace members are the user roster for a specific workspace. A user must already be an org member (`tenant_members` row) before they can be added to a workspace. The last `owner` in a workspace cannot be removed until another member is promoted to owner. Base path: `/api/v1/workspaces/:id/members` To invite a new user who is not yet an org member, use the [Workspace Invites API](/docs/api-reference/invites) instead. Accepting a workspace invite automatically creates the `tenant_members` row when the user is new to the org. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/workspaces/{id}/members` | List workspace members | | `POST` | `/workspaces/{id}/members` | Add an existing org member to the workspace | | `PATCH` | `/workspaces/{id}/members/{user_id}` | Update a member's role | | `DELETE` | `/workspaces/{id}/members/{user_id}` | Remove a member | --- ## Roles | Role | Description | |------|-------------| | `owner` | Full admin including transfer and deletion | | `admin` | Can manage members and resources | | `member` | Can create and manage resources | | `viewer` | Read-only access | --- ## List Members **`GET /api/v1/workspaces/{id}/members`** Returns all members of the workspace. ### Response - `200 OK` ```json { "data": [ { "user_id": "usr_abc123", "email": "alice@example.com", "name": "Alice", "avatar_url": null, "role": "owner", "joined_at": "2026-05-01T10:00:00Z", "added_by": null }, { "user_id": "usr_def456", "email": "bob@example.com", "name": "Bob", "avatar_url": null, "role": "member", "joined_at": "2026-05-10T14:00:00Z", "added_by": "usr_abc123" } ] } ``` ```bash curl https://api.miosa.ai/api/v1/workspaces/{id}/members \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript const client = new Miosa(); const members = await client.workspaceMembers.list('ws-uuid'); console.log(members); ``` ```python client = miosa.Miosa() members = client.workspace_members.list("ws-uuid") print(members) ``` ```go client := miosa.NewClient(os.Getenv("MIOSA_API_KEY")) members, err := client.WorkspaceMembers.List(ctx, "ws-uuid") ``` ```elixir client = Miosa.client("msk_u_...") {:ok, members} = Miosa.WorkspaceMembers.list(client, "ws-uuid") ``` ```java MiosaClient miosa = new MiosaClient("msk_u_..."); List> members = miosa.workspaceMembers().list("ws-uuid"); ``` --- ## Add Member **`POST /api/v1/workspaces/{id}/members`** Adds an existing org member to the workspace with the given role. The user must already hold a `tenant_members` row for this org. If they are not yet an org member, send a workspace invite via `POST /workspaces/{id}/invites` instead - accepting the invite creates the org membership atomically. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `user_id` | UUID | Yes | The user's ID - must already be a tenant member | | `role` | string | Yes | `owner`, `admin`, `member`, or `viewer` | ### Response - `201 Created` ```json { "data": { "user_id": "usr_def456", "workspace_id": "ws-uuid", "role": "member", "joined_at": "2026-05-22T09:00:00Z", "added_by": "usr_abc123" } } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 422 | `NOT_TENANT_MEMBER` | User is not a member of the parent org | | 422 | `MISSING_USER_ID` | `user_id` field missing | | 422 | `MISSING_ROLE` | `role` field missing | | 409 | - | User is already a workspace member | ```bash curl -X POST https://api.miosa.ai/api/v1/workspaces/{id}/members \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"user_id": "usr_def456", "role": "member"}' ``` ```typescript const record = await client.workspaceMembers.add('ws-uuid', { userId: 'usr_def456', role: 'member', }); ``` ```python record = client.workspace_members.add("ws-uuid", user_id="usr_def456", role="member") ``` ```go record, err := client.WorkspaceMembers.Add(ctx, "ws-uuid", miosa.AddWorkspaceMemberInput{ UserID: "usr_def456", Role: "member", }) ``` ```elixir {:ok, record} = Miosa.WorkspaceMembers.add(client, "ws-uuid", "usr_def456", "member") ``` ```java Map record = miosa.workspaceMembers().add("ws-uuid", "usr_def456", "member"); ``` --- ## Update Member Role **`PATCH /api/v1/workspaces/{id}/members/{user_id}`** Changes a workspace member's role. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `role` | string | Yes | New role: `owner`, `admin`, `member`, or `viewer` | ### Response - `200 OK` Updated workspace member record (same shape as the add response). ### Errors | Status | Code | Cause | |--------|------|-------| | 404 | - | User is not a member of this workspace | | 422 | `MISSING_ROLE` | `role` field missing | ```bash curl -X PATCH https://api.miosa.ai/api/v1/workspaces/{id}/members/{user_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"role": "admin"}' ``` ```typescript const updated = await client.workspaceMembers.updateRole('ws-uuid', 'usr_def456', { role: 'admin', }); ``` ```python updated = client.workspace_members.update_role("ws-uuid", "usr_def456", role="admin") ``` ```go updated, err := client.WorkspaceMembers.UpdateRole(ctx, "ws-uuid", "usr_def456", miosa.UpdateWorkspaceMemberRoleInput{Role: "admin"}) ``` ```elixir {:ok, updated} = Miosa.WorkspaceMembers.update_role(client, "ws-uuid", "usr_def456", "admin") ``` ```java Map updated = miosa.workspaceMembers().updateRole("ws-uuid", "usr_def456", "admin"); ``` --- ## Remove Member **`DELETE /api/v1/workspaces/{id}/members/{user_id}`** Removes a user from the workspace. This does not remove them from the parent org. ### Response - `200 OK` ```json { "deleted": true } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 404 | - | User is not a member of this workspace | | 409 | `LAST_OWNER` | Cannot remove the last workspace owner - promote another member first | ```bash curl -X DELETE https://api.miosa.ai/api/v1/workspaces/{id}/members/{user_id} \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` ```typescript await client.workspaceMembers.remove('ws-uuid', 'usr_def456'); ``` ```python client.workspace_members.remove("ws-uuid", "usr_def456") ``` ```go err := client.WorkspaceMembers.Remove(ctx, "ws-uuid", "usr_def456") ``` ```elixir {:ok, _} = Miosa.WorkspaceMembers.remove(client, "ws-uuid", "usr_def456") ``` ```java miosa.workspaceMembers().remove("ws-uuid", "usr_def456"); ``` --- ## Common Errors | Status | Code | Cause | |--------|------|-------| | 403 | - | Workspace belongs to a different tenant | | 404 | - | Workspace or user not found | | 409 | `LAST_OWNER` | Refusing to remove the sole workspace owner | | 422 | `NOT_TENANT_MEMBER` | User must join the org before joining the workspace | | 422 | `MISSING_USER_ID` | `user_id` body field is required | | 422 | `MISSING_ROLE` | `role` body field is required | --- ## See also - [Workspace Invites API](/docs/api-reference/invites) - invite users by email (creates org membership on accept) - [Workspaces API](/docs/api-reference/workspaces) - workspace CRUD - [Error Codes](/docs/api-reference/errors) - full error reference --- # 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 microVM 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 /workspace/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 limits vary by key, tenant policy, and endpoint family. Honor `X-RateLimit-*` and `Retry-After` response headers. 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": "/workspace/projects", "entries": [ { "name": "my-app", "path": "/workspace/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. The same CLI surface is available with: ```bash miosa agent run "Audit the local browser workflow" \ --host host_abc123 \ --agent-profile arp_123 \ --json ``` ### Dispatch an agent **`POST /api/v1/opencomputers/hosts/{id}/agent/dispatch`** | Field | Type | Required | Description | |-------|------|----------|-------------| | `task` | string | Yes | Natural-language task description | | `model` / `model_id` | string | No | Override the default runtime/model | | `tools` | string[] | No | Tool names to expose to the agent session | | `budget.max_steps` | integer | No | Maximum agent steps (default: 30) | | `budget.max_tokens` | integer | No | Maximum token budget (default: 100000) | | `budget.timeout_ms` | integer | No | Session timeout in milliseconds | | `agent_runtime_profile_id` | UUID/string | No | Force a specific runtime profile | | `skip_agent_runtime_profile` | boolean | No | Do not apply the tenant/workspace default profile | | `context` | object | No | Additional context key-value pairs | ```json { "id": "sess_abc", "session_id": "sess_abc", "host_id": "host_abc123", "task": "Run the test suite and fix any failing tests", "status": "running", "sse_url": "/api/v1/opencomputers/hosts/host_abc123/agent/sessions/sess_abc/events", "optimal_session_id": "opt_sess_123", "agent_runtime_profile_id": "arp_123", "runtime_context": { "agent_runtime_profile": { "id": "arp_123", "runtime": "claude-code", "tools": ["browser", "filesystem"], "env_keys": ["ANTHROPIC_API_KEY"] } } } ``` **Session statuses:** `running` | `succeeded` | `failed` | `canceled` ### 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}/events`** ### 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 --- # Preview Tokens API URL: https://miosa.ai/docs/api-reference/preview-tokens Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/preview-tokens Source: src/routes/docs/api-reference/preview-tokens/+page.md Description: Generate short-lived signed tokens for iframe embedding without exposing an API key. Preview tokens (`mp_*`) are short-lived signed credentials that allow you to embed a running sandbox in an ` ``` The token is verified by the sandbox proxy on every request during the token's lifetime. Once it expires, the iframe will show an access-denied page. --- # 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 --- # Quotas API URL: https://miosa.ai/docs/api-reference/quotas Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/quotas Source: src/routes/docs/api-reference/quotas/+page.md Description: Set per-end-user resource limits for white-label multi-tenant deployments. Quotas let you cap how many sandboxes an individual end-user of your product can create and run. They are keyed by `external_user_id` - the same opaque identifier you pass when creating sandboxes on behalf of your users. Base path: `/api/v1/quotas/external/{external_user_id}` Quotas are enforced at sandbox creation time. Setting a quota does not affect sandboxes already running. Deleting a quota removes all limits for that user. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/quotas/external/{external_user_id}` | Get quota and current usage | | `PUT` | `/api/v1/quotas/external/{external_user_id}` | Create or update a quota | | `DELETE` | `/api/v1/quotas/external/{external_user_id}` | Remove quota (unlimited) | --- ## Get Quota **`GET /api/v1/quotas/external/{external_user_id}`** Returns the configured limits and live usage counters for the user. ### Auth ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` ```json { "data": { "external_user_id": "user_clinic_42", "limits": { "max_sandboxes": 10, "max_concurrent": 3, "max_storage_gb": 50, "max_credit_cents": 5000 }, "usage": { "total_sandboxes": 4, "concurrent": 1 } } } ``` | Field | Type | Description | |-------|------|-------------| | `limits.max_sandboxes` | integer\|null | Lifetime sandbox creation limit | | `limits.max_concurrent` | integer\|null | Max sandboxes running simultaneously | | `limits.max_storage_gb` | integer\|null | Total attached storage cap in GiB | | `limits.max_credit_cents` | integer\|null | Spend cap in cents (platform credits) | | `usage.total_sandboxes` | integer | Total sandboxes created by this user | | `usage.concurrent` | integer | Currently running sandboxes | A `null` limit means no cap is applied for that dimension. --- ## Set Quota **`PUT /api/v1/quotas/external/{external_user_id}`** Creates or replaces the quota. All limit fields are optional - omit a field to leave it uncapped. ### Request Body ```json { "max_sandboxes": 10, "max_concurrent": 3, "max_storage_gb": 50, "max_credit_cents": 5000 } ``` ### Response - `200 OK` Same shape as GET response. --- ## Delete Quota **`DELETE /api/v1/quotas/external/{external_user_id}`** Removes all limits for the user. Future sandbox creation is governed only by your tenant plan. ### Response - `204 No Content` --- ## Errors | Status | Code | Cause | |--------|------|-------| | 404 | `not_found` | No quota exists for this `external_user_id` (GET/DELETE) | | 422 | validation errors | A limit value is non-integer or negative | --- ## Examples ```bash # Set curl -X PUT https://api.miosa.ai/api/v1/quotas/external/user_clinic_42 \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{"max_sandboxes": 10, "max_concurrent": 3}' # Get curl https://api.miosa.ai/api/v1/quotas/external/user_clinic_42 \ -H "Authorization: Bearer msk_u_..." # Remove curl -X DELETE https://api.miosa.ai/api/v1/quotas/external/user_clinic_42 \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Set quota when user signs up client.quotas.set("user_clinic_42", max_sandboxes=10, max_concurrent=3) # Check quota + usage quota = client.quotas.get("user_clinic_42") print(quota.usage.concurrent) # Remove limits client.quotas.delete("user_clinic_42") ``` ```typescript const client = new Miosa(); // Set await client.quotas.set('user_clinic_42', { maxSandboxes: 10, maxConcurrent: 3, maxStorageGb: 50, }); // Get const quota = await client.quotas.get('user_clinic_42'); console.log(quota.usage.concurrent); // Remove await client.quotas.delete('user_clinic_42'); ``` --- # 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 --- # Releases API 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: Inspect and activate exact immutable deployment artifacts. A **Release** is the immutable artifact produced from a build or sandbox publish. It records the artifact digest, runtime command, port, health check, source attribution, and readiness state used for an exact promotion. Base path: `/api/v1/deployments/{deployment_id}/releases` ## Endpoints | Method | Path | Purpose | |---|---|---| | `GET` | `/deployments/{deployment_id}/releases` | List releases for one deployment | | `GET` | `/deployments/{deployment_id}/releases/{release_id}` | Inspect one exact release | | `POST` | `/deployments/{deployment_id}/releases/{release_id}/promote` | Activate one exact release | Publish creates the candidate release. Promotion changes the active production artifact only after the requested release is ready and the configured runtime accepts it. ## List releases ```bash curl https://api.miosa.ai/api/v1/deployments/$DEPLOYMENT_ID/releases \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` The response contains `data`, a compatibility `releases` array, and `total`. ```json { "data": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "deployment_id": "660e8400-e29b-41d4-a716-446655440000", "deployment_version_id": "770e8400-e29b-41d4-a716-446655440000", "kind": "dynamic", "state": "ready", "artifact_sha256": "a5e6f0c1...", "start_command": "node server.js", "port": 3000, "health_check_path": "/api/health", "ready_at": "2026-07-24T18:19:48Z", "created_at": "2026-07-24T18:18:06Z" } ], "total": 1 } ``` ## Inspect one release ```bash curl \ https://api.miosa.ai/api/v1/deployments/$DEPLOYMENT_ID/releases/$RELEASE_ID \ -H "Authorization: Bearer $MIOSA_API_KEY" ``` Use this endpoint to confirm the exact artifact digest, runtime contract, source sandbox, build, and readiness state before promotion. ## Promote one exact release Promotion is a mutation and requires an idempotency key. ```bash curl -X POST \ https://api.miosa.ai/api/v1/deployments/$DEPLOYMENT_ID/releases/$RELEASE_ID/promote \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Idempotency-Key: $IDEMPOTENCY_KEY" ``` The response contains the deployment plus the durable operation record. Repeating the same request with the same idempotency key returns the existing result instead of activating the release twice. Promotion uses the deployment's configured runtime product. For App Engine, the release is activated through the workspace App Engine host. For standard MIOSA Deploy, it is activated through the standard dynamic or static runtime. ## CLI ```bash miosa releases list --json miosa releases get --json miosa releases promote-release --yes --json miosa releases rollback --app --yes --json ``` ## Immutability A release is content-addressed by `artifact_sha256`. Changing application content creates a new release. Promotion never mutates the bytes of an existing release. Rollback selects a previously ready release and activates that exact artifact through the same runtime-aware path. ## Permissions | Action | Required scope | |---|---| | List or inspect releases | `deployments:read` | | Promote or roll back | `deployments:write` | ## See also Create candidate releases from sandbox source. Inspect logical deployment versions and their build state. Restore a previously healthy release. --- # Runtime Env URL: https://miosa.ai/docs/api-reference/runtime-env Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/runtime-env Source: src/routes/docs/api-reference/runtime-env/+page.md Description: Store inherited runtime environment variables for sandboxes, computers, agents, and deployments. # Runtime Env Runtime Env stores inherited environment variables for tenant, workspace, and project scopes. Values are encrypted at rest and are never returned in plaintext. MIOSA materializes them into sandboxes, computers, agents, or deployments according to the target you choose. Base path: `/api/v1/runtime-env` Runtime Env can hold provider keys such as Anthropic, OpenAI, Higgsfield, Resend, Stripe, or customer API tokens. Scope them as narrowly as possible and avoid putting tenant admin credentials into broad defaults. ## Endpoints | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/runtime-env` | List inherited env variables | | `POST` | `/api/v1/runtime-env` | Create or update one variable | | `GET` | `/api/v1/runtime-env/{id}` | Show one variable without plaintext | | `DELETE` | `/api/v1/runtime-env/{id}` | Delete one variable | List supports these filters: | Query | Values | |---|---| | `scope` | `tenant`, `workspace`, `project` | | `workspace_id` | Workspace id for workspace-scoped variables | | `project_id` | Project id for project-scoped variables | | `target` | `all`, `sandbox`, `computer`, `agent`, `deployment` | ## Create ```http POST /api/v1/runtime-env Authorization: Bearer msk_u_... Content-Type: application/json ``` ```json { "scope": "workspace", "workspace_id": "ws_123", "target": "sandbox", "name": "ANTHROPIC_API_KEY", "value": "sk-ant-...", "metadata": { "provider": "anthropic", "owner": "clinic-iq" } } ``` Environment variable names must be uppercase shell-safe names matching `/^[A-Z][A-Z0-9_]*$/`. ## Scope model | Scope | Required id | Use it for | |---|---|---| | `tenant` | none | Organization-wide defaults. | | `workspace` | `workspace_id` | A customer/workspace provider key or tool config. | | `project` | `project_id` | A single app or deployment pipeline. | | Target | Receives the variable | |---|---| | `all` | All supported runtime targets. | | `sandbox` | Sandbox sessions and sandbox agent runs. | | `computer` | Desktop computer sessions and computer agent runs. | | `agent` | Agent runtime sessions. | | `deployment` | Production deployment/runtime boot. | Use narrower scopes for spend-capable keys. For example, set `ANTHROPIC_API_KEY` at workspace scope for ClinicIQ instead of tenant scope if that key should only power ClinicIQ sandboxes and computers. ## CLI ```bash miosa runtime-env set ANTHROPIC_API_KEY=sk-ant-... \ --scope workspace \ --workspace ws_123 \ --target sandbox \ --json miosa runtime-env set HIGGSFIELD_API_KEY=hgf_... \ --scope project \ --project proj_123 \ --target agent \ --json miosa runtime-env list --scope workspace --workspace ws_123 --json miosa runtime-env show env_123 --json miosa runtime-env unset env_123 ``` ## Relationship to profiles and connectors Runtime Env, Agent Runtime Profiles, and managed connectors are separate: | Primitive | Owns | |---|---| | Runtime Env | Inherited environment variables and encrypted provider keys. | | Agent Runtime Profile | Runtime choice, tools, connectors, default env, and policy. | | Managed connector | Brokered provider access and scoped token exchange. | Use Runtime Env for direct provider environment variables. Use connectors when MIOSA should broker provider access or restrict egress. Use profiles to attach the right defaults to new sandboxes, computers, and agent runs. ## Next Standardize runtimes, tools, connectors, and policy for each workspace or project. Dispatch prompts with inherited env and runtime profile resolution. Put inherited env, profiles, groups, events, and artifacts together. --- # Sandbox Env API URL: https://miosa.ai/docs/api-reference/sandbox-env Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandbox-env Source: src/routes/docs/api-reference/sandbox-env/+page.md Description: CRUD for per-sandbox environment variables, encrypted at rest. The sandbox env API manages a key-value store of environment variables attached to a sandbox. Variables are encrypted at rest and injected into the sandbox process environment on start. The API never returns plaintext values - only key names and metadata. Base path: `/api/v1/sandboxes/{id}/env` Variable names must match `[A-Z][A-Z0-9_]*` - uppercase letters, digits, and underscores, starting with a letter. This mirrors POSIX shell environment variable conventions. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/sandboxes/{id}/env` | List all env vars (names only, no values) | | `PUT` | `/api/v1/sandboxes/{id}/env` | Bulk set or update env vars | | `DELETE` | `/api/v1/sandboxes/{id}/env/{key}` | Delete a single env var | --- ## List Env Vars **`GET /api/v1/sandboxes/{id}/env`** Returns all variable names. Values are never returned. ### Auth Requires `sandboxes:read` scope. ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` ```json { "data": [ { "key": "DATABASE_URL", "encrypted": true }, { "key": "API_SECRET", "encrypted": true }, { "key": "LOG_LEVEL", "encrypted": false } ] } ``` --- ## Set Env Vars **`PUT /api/v1/sandboxes/{id}/env`** Bulk upsert. Existing keys are updated; new keys are created. Keys not present in the request are left unchanged. ### Auth Requires `sandboxes:write` scope. ``` Authorization: Bearer msk_... ``` ### Request Body ```json { "vars": [ { "key": "DATABASE_URL", "value": "postgres://...", "encrypted": true }, { "key": "LOG_LEVEL", "value": "debug" } ] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `vars[].key` | string | Yes | Variable name (`[A-Z][A-Z0-9_]*`) | | `vars[].value` | string | Yes | Variable value | | `vars[].encrypted` | boolean | No | Store encrypted at rest (default: `true`) | ### Response - `200 OK` ```json { "data": [ { "key": "DATABASE_URL", "encrypted": true }, { "key": "LOG_LEVEL", "encrypted": false } ] } ``` --- ## Delete Env Var **`DELETE /api/v1/sandboxes/{id}/env/{key}`** Removes a single variable. ### Response - `200 OK` ```json { "deleted": true, "name": "LOG_LEVEL" } ``` --- ## Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_ID` | Sandbox ID is not a valid UUID | | 400 | `invalid vars` | `vars` missing or a key fails name validation | | 404 | `NOT_FOUND` | Sandbox does not exist or wrong tenant | | 404 | `NOT_FOUND` | Key does not exist (on DELETE) | --- ## Examples ```bash # Set curl -X PUT https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/env \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{ "vars": [ {"key": "DATABASE_URL", "value": "postgres://user:pass@host/db"}, {"key": "LOG_LEVEL", "value": "info"} ] }' # List curl https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/env \ -H "Authorization: Bearer msk_u_..." # Delete curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/env/LOG_LEVEL \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Set client.sandboxes.env.set("sbx_abc123", { "DATABASE_URL": "postgres://user:pass@host/db", "LOG_LEVEL": "info", }) # List (names only) keys = client.sandboxes.env.list("sbx_abc123") # Delete one client.sandboxes.env.delete("sbx_abc123", "LOG_LEVEL") ``` ```typescript const client = new Miosa(); // Set await client.sandboxes.env.set('sbx_abc123', { DATABASE_URL: 'postgres://user:pass@host/db', LOG_LEVEL: 'info', }); // List const { data } = await client.sandboxes.env.list('sbx_abc123'); // Delete await client.sandboxes.env.delete('sbx_abc123', 'LOG_LEVEL'); ``` --- # Sandbox Files (Advanced) API URL: https://miosa.ai/docs/api-reference/sandbox-files-advanced Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandbox-files-advanced Source: src/routes/docs/api-reference/sandbox-files-advanced/+page.md Description: File tree traversal, batch write, and SSE file-watch for IDE-grade sandbox file operations. These advanced file operations extend the core [Files API](/docs/api-reference/files) with IDE-oriented capabilities: recursive tree listing, atomic batch writes, and a live file-change event stream. Base path: `/api/v1/sandboxes/{id}/files` These routes use `:sandbox_id` as the path parameter internally but are accessed as `/sandboxes/{id}/files/...` - the `{id}` segment is the sandbox UUID. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/sandboxes/{id}/files/tree` | Recursive file tree | | `POST` | `/api/v1/sandboxes/{id}/files/write-many` | Batch write multiple files | | `GET` | `/api/v1/sandboxes/{id}/files/watch` | SSE stream of file-change events | --- ## File Tree **`GET /api/v1/sandboxes/{id}/files/tree`** Returns a recursive directory listing up to a configurable depth. Useful for populating an IDE file-explorer panel. ### Auth ``` Authorization: Bearer msk_... ``` ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `path` | string | `/workspace` | Root directory to list | | `depth` | integer | `3` | Max traversal depth (1-10) | | `include_hidden` | boolean | `false` | Include dot-files and dot-directories | ### Response - `200 OK` ```json { "data": { "path": "/workspace", "type": "directory", "children": [ { "name": "src", "path": "/workspace/src", "type": "directory", "children": [ { "name": "index.ts", "path": "/workspace/src/index.ts", "type": "file", "size": 1842, "modified_at": "2026-05-26T09:12:01Z" } ] }, { "name": "package.json", "path": "/workspace/package.json", "type": "file", "size": 512, "modified_at": "2026-05-25T18:00:00Z" } ] } } ``` --- ## Batch Write **`POST /api/v1/sandboxes/{id}/files/write-many`** Writes multiple files in a single request. All writes are applied atomically from the guest's perspective - either all succeed or an error is returned for the failing file. Maximum 50 files per request. Files larger than 90 KB should be uploaded individually via `POST /files/write`. ### Request Body ```json { "files": [ { "path": "/workspace/src/index.ts", "content": "export const hello = () => 'world';\n", "encoding": "utf8" }, { "path": "/workspace/src/utils.ts", "content": "ZXhwb3J0IGZ1bmN0aW9uIG5vb3AoKSB7fQo=", "encoding": "base64" } ] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `files[].path` | string | Yes | Absolute path inside the sandbox | | `files[].content` | string | Yes | File content (UTF-8 text or base64-encoded binary) | | `files[].encoding` | string | No | `"utf8"` (default) or `"base64"` | ### Response - `200 OK` ```json { "data": { "written": 2, "paths": [ "/workspace/src/index.ts", "/workspace/src/utils.ts" ] } } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_BODY` | `files` is missing or not an array | | 400 | `TOO_MANY_FILES` | More than 50 files in one request | | 400 | `INVALID_PATH` | A path contains a null byte or traverses outside the sandbox | --- ## File Watch (SSE) **`GET /api/v1/sandboxes/{id}/files/watch`** Streams file-system change events as SSE. Useful for syncing editor state in real time. ### Auth ``` Authorization: Bearer msk_... ``` (Browser `EventSource` cannot set this header - use a server-side client or a scoped session token.) ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `path` | string | `/workspace` | Directory to watch | ### Response - `200 OK` (`text/event-stream`) ``` id: wev_001 event: file.changed data: {"path":"/workspace/src/index.ts","event":"modified","ts":"2026-05-26T10:14:23Z"} id: wev_002 event: file.changed data: {"path":"/workspace/src/new-file.ts","event":"created","ts":"2026-05-26T10:14:24Z"} : heartbeat ``` **Event values for `data.event`:** | Value | Trigger | |-------|---------| | `created` | New file or directory | | `modified` | File content changed | | `deleted` | File or directory removed | | `renamed` | File moved or renamed | --- ## Examples ```bash # File tree (depth 2) curl "https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/files/tree?depth=2" \ -H "Authorization: Bearer msk_u_..." # Batch write curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/files/write-many \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{ "files": [ {"path": "/workspace/hello.ts", "content": "console.log(\"hello\");\n"} ] }' # File watch (SSE) curl -N -H "Authorization: Bearer msk_u_..." \ -H "Accept: text/event-stream" \ "https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/files/watch?path=/workspace/src" ``` ```python client = miosa.Miosa() # Tree tree = client.sandboxes.files.tree("sbx_abc123", path="/workspace", depth=3) # Batch write client.sandboxes.files.write_many("sbx_abc123", [ {"path": "/workspace/index.ts", "content": "export {};"}, {"path": "/workspace/utils.ts", "content": "export function noop() {}"}, ]) ``` ```typescript const client = new Miosa(); // File tree const tree = await client.sandboxes.files.tree('sbx_abc123', { path: '/workspace', depth: 3, includeHidden: false, }); // Batch write await client.sandboxes.files.writeMany('sbx_abc123', [ { path: '/workspace/index.ts', content: 'export {};' }, { path: '/workspace/utils.ts', content: 'export function noop() {}' }, ]); ``` --- # Sandbox Processes API URL: https://miosa.ai/docs/api-reference/sandbox-processes Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandbox-processes Source: src/routes/docs/api-reference/sandbox-processes/+page.md Description: Start, monitor, and stream logs for long-running background processes inside sandboxes. The processes API lets you launch long-running background commands inside a sandbox and inspect their output - useful for starting dev servers, watchers, or any process that runs beyond a single exec call. Base path: `/api/v1/sandboxes/{id}/processes` Processes are managed by the sandbox runtime and survive for the lifetime of the sandbox. They are not preserved across snapshots or forks. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/api/v1/sandboxes/{id}/processes` | Start a process | | `GET` | `/api/v1/sandboxes/{id}/processes` | List processes | | `GET` | `/api/v1/sandboxes/{id}/processes/{pid}` | Get process status | | `DELETE` | `/api/v1/sandboxes/{id}/processes/{pid}` | Stop a process | | `GET` | `/api/v1/sandboxes/{id}/processes/{pid}/logs` | Fetch recent log lines | | `GET` | `/api/v1/sandboxes/{id}/processes/{pid}/stream` | SSE live log stream | --- ## Start a Process **`POST /api/v1/sandboxes/{id}/processes`** ### Auth ``` Authorization: Bearer msk_... ``` ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `cmd` | string | Yes | Command to run (passed to `/bin/sh -c`) | | `cwd` | string | No | Working directory (default: `/workspace`) | | `env` | object | No | Additional environment variables | | `name` | string | No | Human-readable label | ```json { "cmd": "npm run dev -- --port 3000", "cwd": "/workspace", "name": "dev-server" } ``` ### Response - `201 Created` ```json { "data": { "pid": "proc_01hwxyz...", "name": "dev-server", "cmd": "npm run dev -- --port 3000", "cwd": "/workspace", "status": "running", "started_at": "2026-05-26T10:14:23Z", "exited_at": null, "exit_code": null } } ``` --- ## List Processes **`GET /api/v1/sandboxes/{id}/processes`** ### Response - `200 OK` ```json { "data": [ { "pid": "proc_01hwxyz...", "name": "dev-server", "status": "running", "started_at": "2026-05-26T10:14:23Z", "exited_at": null, "exit_code": null } ] } ``` --- ## Get Process Status **`GET /api/v1/sandboxes/{id}/processes/{pid}`** ### Response - `200 OK` ```json { "data": { "pid": "proc_01hwxyz...", "name": "dev-server", "cmd": "npm run dev -- --port 3000", "cwd": "/workspace", "status": "exited", "started_at": "2026-05-26T10:14:23Z", "exited_at": "2026-05-26T10:22:01Z", "exit_code": 0 } } ``` **Process status values:** | Status | Description | |--------|-------------| | `running` | Process is alive | | `exited` | Process exited normally | | `killed` | Process was stopped via DELETE | | `error` | Process failed to start | --- ## Stop a Process **`DELETE /api/v1/sandboxes/{id}/processes/{pid}`** Sends `SIGTERM` followed by `SIGKILL` after a grace period. ### Response - `204 No Content` --- ## Fetch Logs **`GET /api/v1/sandboxes/{id}/processes/{pid}/logs`** Returns the last N lines of stdout+stderr. ### Query Parameters | Parameter | Type | Default | Description | |-----------|------|---------|-------------| | `tail` | integer | `200` | Number of lines to return | ### Response - `200 OK` ```json { "data": { "lines": [ " > Local: http://localhost:3000", " > Network: http://0.0.0.0:3000", " ready in 743ms" ] } } ``` --- ## Live Log Stream (SSE) **`GET /api/v1/sandboxes/{id}/processes/{pid}/stream`** Streams stdout+stderr as SSE. Uses `text/event-stream` - requires Bearer token auth. ``` id: log_001 event: log data: {"line":" ready in 743ms","ts":"2026-05-26T10:14:24Z"} : heartbeat ``` --- ## Errors | Status | Code | Cause | |--------|------|-------| | 400 | `INVALID_ID` | Sandbox ID is not a valid UUID | | 404 | `NOT_FOUND` | Sandbox or process does not exist | | 409 | `SANDBOX_NOT_RUNNING` | Sandbox must be in `running` state to start processes | | 500 | `PROCESS_START_FAILED` | Runtime failed to spawn the process | --- ## Examples ```bash # Start curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/processes \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{"cmd": "npm run dev -- --port 3000", "name": "dev-server"}' # List curl https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/processes \ -H "Authorization: Bearer msk_u_..." # Logs curl "https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/processes/proc_01hwxyz/logs?tail=50" \ -H "Authorization: Bearer msk_u_..." # Stop curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/processes/proc_01hwxyz \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Start a dev server proc = client.sandboxes.processes.start( "sbx_abc123", cmd="npm run dev -- --port 3000", name="dev-server", ) # Wait a moment then check logs time.sleep(3) logs = client.sandboxes.processes.logs("sbx_abc123", proc.pid, tail=50) for line in logs: print(line) # Stop client.sandboxes.processes.stop("sbx_abc123", proc.pid) ``` ```typescript const client = new Miosa(); // Start const proc = await client.sandboxes.processes.start('sbx_abc123', { cmd: 'npm run dev -- --port 3000', name: 'dev-server', }); // Poll status let status = proc.status; while (status === 'running') { await new Promise(r => setTimeout(r, 2000)); const updated = await client.sandboxes.processes.get('sbx_abc123', proc.pid); status = updated.status; } // Logs const { data } = await client.sandboxes.processes.logs('sbx_abc123', proc.pid); console.log(data.lines.join('\n')); ``` --- # Sandbox Shares API URL: https://miosa.ai/docs/api-reference/sandbox-shares Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandbox-shares Source: src/routes/docs/api-reference/sandbox-shares/+page.md Description: Create and manage public share URLs for sandboxes. Sandbox shares are publicly accessible URLs that give read-only access to a running sandbox. Unlike preview tokens, share URLs are not scoped to an iframe and do not require the recipient to hold any credentials. Share tokens use the prefix `ms_` followed by a base64url-encoded payload. The public URL pattern is: ``` https://{sandbox-id}.sandboxes.miosa.ai?ms=ms_ ``` --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `POST` | `/api/v1/sandboxes/{id}/shares` | Create a share URL | | `GET` | `/api/v1/sandboxes/{id}/shares` | List active shares | | `DELETE` | `/api/v1/sandboxes/{id}/shares/{share_id}` | Revoke a share | --- ## Create a Share **`POST /api/v1/sandboxes/{id}/shares`** Creates a public share link. The `share_url` field is safe to distribute - it contains the token as a query parameter. ### Auth ``` Authorization: Bearer msk_... ``` ### Request Body | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | `expires_in` | integer | No | `3600` | TTL in seconds | | `scope` | string | No | `"read"` | Must be `"read"` (only supported value) | ```json { "expires_in": 86400 } ``` ### Response - `201 Created` ```json { "share_id": "shr_01hx9m7n3k...", "share_url": "https://sbx-abc123.sandboxes.miosa.ai?ms=ms_eyJ...", "expires_at": "2026-05-27T12:00:00Z", "scope": "read" } ``` | Field | Type | Description | |-------|------|-------------| | `share_id` | string | Opaque share identifier used to revoke | | `share_url` | string | Publicly accessible URL with token embedded | | `expires_at` | string | ISO 8601 expiry, or `null` for no expiry | | `scope` | string | Always `"read"` | --- ## List Shares **`GET /api/v1/sandboxes/{id}/shares`** Returns all active (non-revoked, non-expired) shares for the sandbox. ### Response - `200 OK` ```json { "data": [ { "share_id": "shr_01hx9m7n3k...", "share_url": "https://sbx-abc123.sandboxes.miosa.ai?ms=ms_eyJ...", "expires_at": "2026-05-27T12:00:00Z", "scope": "read" } ] } ``` --- ## Revoke a Share **`DELETE /api/v1/sandboxes/{id}/shares/{share_id}`** Immediately invalidates the share. Any visitor using the share URL will receive a 403 after revocation. ### Response - `204 No Content` No response body. --- ## Errors | Status | Code | Cause | |--------|------|-------| | 404 | `sandbox_not_found` | Sandbox does not exist or wrong tenant | | 403 | `forbidden` | Tenant mismatch | | 404 | `not_found` | Share ID does not exist (on DELETE) | | 422 | `invalid_scope` | `scope` value is not `"read"` | --- ## Examples ```bash # Create curl -X POST https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/shares \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{"expires_in": 86400}' # List curl https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/shares \ -H "Authorization: Bearer msk_u_..." # Revoke curl -X DELETE https://api.miosa.ai/api/v1/sandboxes/sbx_abc123/shares/shr_01hx9m7n3k \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Create share = client.sandboxes.shares.create("sbx_abc123", expires_in=86400) print(share.share_url) # List shares = client.sandboxes.shares.list("sbx_abc123") # Revoke client.sandboxes.shares.delete("sbx_abc123", share.share_id) ``` ```typescript const client = new Miosa(); // Create const share = await client.sandboxes.shares.create('sbx_abc123', { expiresIn: 86400, }); console.log(share.shareUrl); // List const shares = await client.sandboxes.shares.list('sbx_abc123'); // Revoke await client.sandboxes.shares.delete('sbx_abc123', share.shareId); ``` --- # Sandbox Templates API URL: https://miosa.ai/docs/api-reference/sandbox-templates Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/sandbox-templates Source: src/routes/docs/api-reference/sandbox-templates/+page.md Description: Browse the template marketplace and launch sandboxes from pre-built images. Sandbox templates are pre-built images with toolchains, dependencies, and configuration baked in. Launching from a template is faster than installing packages at runtime and ensures reproducible environments. Templates are read-only marketplace items managed by MIOSA. To build your own custom templates, see the [Templates guide](/docs/develop/templates). --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/sandbox-templates` | List available templates | | `GET` | `/api/v1/sandbox-templates/{id}` | Get a specific template | | `POST` | `/api/v1/sandboxes` | Create sandbox from a template (via `template_id`) | --- ## List Templates **`GET /api/v1/sandbox-templates`** Returns all templates available to your tenant, including official MIOSA templates and any custom templates you have built. ### Auth ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` ```json { "data": [ { "id": "tpl_debian12", "name": "Debian 12 (Bookworm)", "slug": "debian-12-sandbox-v8", "description": "Debian Bookworm with Node 20, Python 3.11, Go 1.22, and Rust 1.78 pre-installed.", "tags": ["debian", "node", "python", "go", "official"], "memory_mb": 4096, "vcpus": 2, "created_at": "2026-05-11T00:00:00Z", "is_official": true }, { "id": "tpl_nextjs", "name": "Next.js 14", "slug": "nextjs-14", "description": "Next.js 14 App Router starter with Tailwind CSS.", "tags": ["nextjs", "react", "typescript"], "memory_mb": 4096, "vcpus": 2, "created_at": "2026-04-01T00:00:00Z", "is_official": true } ] } ``` | Field | Type | Description | |-------|------|-------------| | `id` | string | Template ID | | `name` | string | Human-readable template name | | `slug` | string | Short identifier used in the `image` field | | `description` | string | What is pre-installed | | `tags` | string[] | Searchable tags | | `memory_mb` | integer | Default sandbox memory allocation | | `vcpus` | integer | Default vCPU count | | `is_official` | boolean | MIOSA-maintained template | --- ## Get Template **`GET /api/v1/sandbox-templates/{id}`** Returns a single template by ID. ### Response - `200 OK` Same shape as a single item from the list response. --- ## Create Sandbox from Template Use the standard sandbox creation endpoint with the template `slug` as the `image` field: **`POST /api/v1/sandboxes`** ```json { "image": "debian-12-sandbox-v8", "memory_mb": 4096, "vcpus": 2 } ``` See the [Sandboxes API](/docs/api-reference/sandboxes) for the full sandbox creation reference. --- ## Errors | Status | Code | Cause | |--------|------|-------| | 404 | `not_found` | Template ID does not exist | --- ## Examples ```bash # List templates curl https://api.miosa.ai/api/v1/sandbox-templates \ -H "Authorization: Bearer msk_u_..." # Get one template curl https://api.miosa.ai/api/v1/sandbox-templates/tpl_debian12 \ -H "Authorization: Bearer msk_u_..." # Create sandbox from template curl -X POST https://api.miosa.ai/api/v1/sandboxes \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{"image": "debian-12-sandbox-v8"}' ``` ```python client = miosa.Miosa() # Browse templates templates = client.sandbox_templates.list() for t in templates: print(t.slug, "-", t.description) # Launch from template sandbox = client.sandboxes.create(image="debian-12-sandbox-v8") ``` ```typescript const client = new Miosa(); // Browse const templates = await client.sandboxTemplates.list(); for (const t of templates) { console.log(t.slug, '-', t.description); } // Launch const sandbox = await client.sandboxes.create({ image: 'debian-12-sandbox-v8', }); ``` --- # 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 - persistent-by-default isolated microVM workspaces. Sandboxes are persistent-by-default isolated microVM workspaces that restore from pre-seeded snapshots, 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://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 and sizes **`GET /api/v1/templates`** This is the canonical catalog for sandbox template IDs, template defaults, per-template size support, readiness, and exact versioned resource contracts. Clients need no secondary catalog endpoint to discover or admit a sandbox configuration. ```bash curl https://api.miosa.ai/api/v1/templates ``` ### Response - `200 OK` ```json { "data": [ { "id": "miosa-sandbox", "name": "MIOSA Sandbox", "product": "sandbox", "default_size": "small", "primitive": "sandbox", "sdk_name": "miosa-sandbox", "cli_name": "miosa-sandbox", "readiness": "fast_ready", "sizes": [ { "size": "small", "state": "fast_ready", "resource_contract": { "contract_id": "sandbox/small@v1", "contract_version": "v1", "product": "sandbox", "size": "small", "vcpus": 2, "memory_mb": 4096, "disk_size_mb": 10240 } } ] } ], "templates": [ { "id": "miosa-sandbox", "name": "MIOSA Sandbox", "product": "sandbox", "default_size": "small", "primitive": "sandbox", "sdk_name": "miosa-sandbox", "cli_name": "miosa-sandbox", "readiness": "fast_ready", "sizes": [ { "size": "small", "state": "fast_ready" } ] } ], "shape_contracts": { "sandbox": [], "computer": [] }, "readiness_states": ["fast_ready", "cold_boot_only", "missing"] } ``` `fast_ready` sizes are creatable through the fast start path. `cold_boot_only` sizes are also creatable but start more slowly. `missing` means unavailable and must not be submitted. Use that template's `default_size` when the caller does not have a stronger requirement. Do not combine a size advertised by one template with another template. --- ## Custom templates The canonical public V1 contract exposes only promoted templates returned by `GET /api/v1/templates`. Standalone custom-template build, validation, log, retry, and cancellation endpoints are not part of that contract. Do not make a public integration depend on those operations. --- ## Configuration and request parameters `GET /api/v1/templates` returns platform configuration: available templates, the default size, readiness, and versioned resource contracts. Applications select that configuration with sandbox parameters; they do not send internal host, provider, network, or placement fields. Prefer `size` for create requests. The default `small` contract is 2 vCPU, 4096 MiB RAM, and 10240 MiB disk. `xs` is selectable when the catalog reports it as available. Compatibility resource fields must include `cpu_count`, `memory_mb`, and `disk_size_mb` together and exactly match one published contract. Responses include the resolved contract under `resource_contract`. Contract IDs are versioned, for example `sandbox/small@v1`; persist that identity when usage or audit records must remain reproducible. ## 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 production alias `miosa-sandbox` resolves to a certified immutable artifact generation. ### Auth Bearer token required. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `template_id` | string | No | Boot template. Defaults to `miosa-sandbox`. See available templates below. | | `size` | string | No | Canonical resource configuration. Defaults to `small` (`2 vCPU`, `4096 MiB`, `10240 MiB` disk). | | `cpu_count` | integer | No | Exact vCPU compatibility input. Supply the full matching resource triple. | | `memory_mb` | integer | No | Exact RAM override. It must match the selected canonical size. | | `disk_size_mb` | integer | No | Exact disk override. It must match the selected canonical size. `disk_mb` is deprecated. | | `timeout_sec` | integer | No | Active-session timeout. Defaults to `3600`; minimum `1`, maximum `86400`. | | `always_on` | boolean | No | Disable timeout and idle-timeout enforcement until an explicit lifecycle action. Defaults to `false` and remains subject to policy. | | `persistent` | boolean | No | Defaults to `true`. Preserve filesystem state across stop/timeout and allow resume. Set `false` only for disposable one-off jobs. | | `idle_timeout_sec` | integer | No | Seconds without activity before auto-stop. `0`, the default, disables idle timeout. | | `env` | object | No | Key-value env vars injected at boot. | | `metadata` | object | No | Arbitrary caller-supplied metadata stored on the record. | | `agent_runtime_profile_id` | UUID/string | No | Force a specific agent runtime profile for this sandbox. Alias: `agent_profile_id`. | | `skip_agent_runtime_profile` | boolean | No | If `true`, do not apply the tenant/workspace default runtime profile. | | `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. | If a default agent runtime profile exists for the workspace or tenant, MIOSA applies it automatically during create. Profile env vars are merged with request env vars, request env wins on conflict, profile metadata is recorded under `metadata.agent_runtime_profile`, and profile connectors are attached through egress placeholder tokens rather than copied as plaintext secrets. **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, "size": "small", "resource_contract": { "id": "sandbox/small@v1", "product": "sandbox", "size": "small", "version": "v1", "vcpus": 2, "memory_mb": 4096, "disk_size_mb": 10240 }, "cpu_count": 2, "memory_mb": 4096, "disk_size_mb": 10240, "preview_url": "https://sbx01j9x.sandbox.miosa.ai", "timeout_sec": 3600, "timeout_remaining_ms": null, "always_on": false, "persistent": true, "idle_timeout_sec": 0, "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). | --- ## Create and Run **`POST /api/v1/sandboxes/run`** Creates a sandbox, waits for it to become `running`, executes the first command, and returns the sandbox record plus the command result in one request. Use this for agent loops where the first useful action is "create a sandbox and run code now." This is the production fast path for agents that need a sandbox and an immediate first command. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/run \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: run-2026-06-09-001" \ -d '{ "template_id": "miosa-sandbox", "command": "python3 -c \"print(1+1)\"", "size": "small", "timeout_sec": 300, "wait_timeout_ms": 30000, "metadata": { "agent_run": "abc123" } }' ``` ### Request Body `/run` accepts the same sandbox creation fields as `POST /api/v1/sandboxes`, plus: | Field | Type | Required | Description | |-------|------|----------|-------------| | `command` | string | Yes | First command to execute after the sandbox reaches `running`. | | `cwd` | string | No | Working directory for the command. Defaults to `/workspace`. | | `timeout` | integer | No | Command timeout in milliseconds. | | `wait_timeout_ms` | integer | No | Max time to wait for sandbox readiness before returning `504 SANDBOX_READY_TIMEOUT`. | ### Response - `201 Created` ```json { "data": { "id": "sbx_01j9xr2t4fk8me3n5q", "state": "running", "ready": true, "template_id": "miosa-sandbox", "cpu_count": 2, "memory_mb": 4096 }, "exec": { "stdout": "2\n", "stderr": "", "exit_code": 0 }, "timings": { "server_wait_and_exec_ms": 512 } } ``` ### Errors | Status | Code | Cause | |--------|------|-------| | 400 | `MISSING_PARAM` | `command` is missing. | | 400 | `INVALID_TEMPLATE` | `template_id` is not recognized. | | 402 | `INSUFFICIENT_CREDITS` | Not enough credits to provision the VM. | | 409 | `SANDBOX_LIMIT_EXCEEDED` | Tenant has reached the concurrent sandbox limit. | | 409 | `SANDBOX_NOT_RUNNING` | The sandbox failed to reach a running state for exec. | | 502 | `SANDBOX_BOOT_FAILED` | VM boot failed before the command could run. | | 502 | `AGENT_UNAVAILABLE` | The sandbox agent was not reachable for exec. | | 504 | `SANDBOX_READY_TIMEOUT` | The sandbox did not become ready before `wait_timeout_ms`. | --- ## 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.ai", "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", "size": "small", "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": 2, "memory_mb": 4096, "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}`** Permanently deletes the sandbox, removes saved state, and settles billing. Use `POST /pause` when the user is done for now but may resume later. ### 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" ``` --- ## Export Files Exports are for files an agent created inside the sandbox that your product needs to show or download: static HTML, PDFs, CSVs, screenshots, generated reports, ZIPs, and build folders. **`POST /api/v1/sandboxes/{id}/exports`** Creates a ready export descriptor. This does not move the file out of the sandbox; it returns download URLs backed by the sandbox filesystem. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `path` | string | Yes, unless `paths` is set | Single file path inside the sandbox. | | `paths` | array | Yes, unless `path` is set | Multiple file paths to package as a tar.gz archive. | | `label` | string | No | Human-readable export label. | | `filename` | string | No | Suggested filename for download responses. | ### Response - `200 OK` ```json { "data": { "id": "exp_01j9xr2t4fk8me3n5q", "sandbox_id": "sbx_01j9xr2t4fk8me3n5q", "label": "homepage build", "status": "ready", "files": [ { "path": "/workspace/dist/index.html", "filename": "index.html", "download_url": "https://api.miosa.ai/api/v1/sandboxes/sbx_.../exports/download?path=..." } ], "archive_download_url": "https://api.miosa.ai/api/v1/sandboxes/sbx_.../exports/download?paths%5B%5D=...", "created_at": "2026-06-14T21:00:00Z" } } ``` **`GET /api/v1/sandboxes/{id}/exports/download?path=/workspace/report.pdf`** Downloads one file as binary. **`GET /api/v1/sandboxes/{id}/exports/download?paths[]=/workspace/dist/index.html&paths[]=/workspace/dist/app.js`** Downloads multiple paths as a tar.gz archive. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/$SBX/exports \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"paths":["/workspace/dist/index.html","/workspace/dist/app.js"],"label":"build"}' curl "https://api.miosa.ai/api/v1/sandboxes/$SBX/exports/download?path=/workspace/report.pdf" \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -o report.pdf ``` --- ## 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.ai" } ``` 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. --- ## Publish a Sandbox **`POST /api/v1/sandboxes/{id}/publish`** Freezes sandbox source, creates an immutable release and deployment version, verifies the candidate, and promotes it behind a stable deployment URL. Use this endpoint for production publishing. The CLI is the recommended happy path: ```bash miosa sandbox publish \ --app \ --docker-deploy \ --wait \ --json ``` Omit `--app` for the first publish. Include it for updates so MIOSA preserves the deployment ID and canonical URL. See [Publishing](/docs/deploy/publishing/) for request options, proof, and failure behavior. ## Promote a Sandbox Runtime to a Deployment **`POST /api/v1/sandboxes/{id}/deploy`** This compatibility bridge routes a persistent deployment URL directly to a running sandbox. The sandbox remains the runtime behind the route and its auto-destroy timer is cancelled. Use it only when the sandbox itself must remain the runtime. For immutable production versions and App Engine, use `/publish`. ### 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" ``` --- ## Extend the Timeout **`POST /api/v1/sandboxes/{id}/extend`** Replaces the active-session timeout with `timeout_sec` from 1 through 86400. The operation does not add duration to the old value. Omitting the body preserves the current timeout. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/extend \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"timeout_sec":7200}' ``` --- ## Get Sandbox Usage **`GET /api/v1/sandboxes/{id}/usage`** Returns measured runtime, provisioned vCPU time, active CPU, network traffic, estimated cost, and timeout visibility. ```json { "data": { "sandbox_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "state": "running", "runtime_sec": 47, "provisioned_vcpu_ms": 94000, "active_cpu_ms": 12000, "network_ingress_bytes": 2048, "network_egress_bytes": 4096, "measurement_status": { "active_cpu": "measured", "network": "measured", "provisioned_resources": "measured" }, "estimated_cost_cents": 1, "timeout_sec": 3600, "timeout_remaining_ms": 3553000 } } ``` Interpret nullable measurements through `measurement_status`. `unavailable` means no trustworthy value exists and the related field is `null`, not zero. `stale` means the retained value is not current and must be labeled as such. `timeout_remaining_ms` can be `null` before start or when timeout enforcement is disabled. --- ## Pause and Resume **`POST /api/v1/sandboxes/{id}/pause`** suspends guest vCPU execution for a running persistent sandbox while preserving its workspace. It returns `409` if the sandbox is not running. **`POST /api/v1/sandboxes/{id}/resume`** transitions a paused sandbox back to `running` and returns `409` if it is not paused. Command and file operations can automatically resume a paused persistent sandbox before dispatch. ```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" \ -H "Idempotency-Key: resume-session-001" ``` --- ## Fork a Sandbox **`POST /api/v1/sandboxes/{id}/fork`** creates a new sandbox from a copy-on-write snapshot of a running sandbox. The optional body accepts `timeout_sec` and `template_id` for the new sandbox. ```bash curl -X POST https://api.miosa.ai/api/v1/sandboxes/{id}/fork \ -H "Authorization: Bearer $MIOSA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: fork-experiment-001" \ -d '{"timeout_sec":3600}' ``` Canonical public V1 exposes standalone snapshot create, list, inspect, delete, and restore operations. Use fork when a customer needs an independent branch of current running state without managing the intermediate snapshot directly. --- ## 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. | | `size` | string | Canonical named size: `xs`, `small`, `medium`, `large`, or `xl`. | | `resource_contract` | object | Versioned resolved contract with ID, product, size, vCPU, memory, and disk. | | `cpu_count` | integer | Allocated vCPUs. | | `memory_mb` | integer | Allocated RAM in MB. | | `disk_size_mb` | integer | Allocated root disk in MB. | | `ready` | boolean | Whether command 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 for the active session. Persistent timeout pauses; non-persistent timeout destroys. | | `timeout_remaining_ms` | integer \| null | Remaining active-session time; null before start or when timeout enforcement is disabled. | | `always_on` | boolean | Whether timeout and idle-timeout enforcement are disabled. | | `persistent` | boolean | Whether the sandbox preserves filesystem state across stop/timeout. | | `idle_timeout_sec` | integer | Seconds without activity before auto-stop; zero disables it. | | `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 → paused running → paused → running running → destroyed * → error (terminal) ``` | State | Description | |-------|-------------| | `provisioning` | VM is booting. Exec and file operations are not yet available. | | `running` | VM is reachable. All operations allowed. | | `paused` | Compute is stopped and filesystem state is preserved. 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 | |-------|-------| | Request rate | Returned through `X-RateLimit-*` headers; varies by key and policy | | Concurrent sandboxes per tenant | 10 (contact support to increase) | | `timeout_sec` | 1-86400 seconds; default 3600 | | Default resource shape | `small`: 2 vCPU, 4 GiB RAM, 10 GiB disk | | Named resource schema | `xs` through `xl`; actual availability comes from `GET /templates` and tenant policy | | 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 computer command service. 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: '/workspace/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": "/workspace/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: `/workspace`) | | `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": "/workspace/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": "/workspace/app/server", "env": {"PORT": "3000", "NODE_ENV": "production"}, "cwd": "/workspace/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: '/workspace/app/server.js', content: serverJsContent, }); // Register and start const svc = await client.services.create(computerId, { name: 'node-app', command: 'node /workspace/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 microVM 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. This page documents desktop Computer snapshots only. Canonical sandbox V1 does not expose direct snapshot CRUD; use [Persistence, pause, and forks](/docs/develop/snapshots/) and the [fork endpoint](/docs/api-reference/sandboxes/#fork-a-sandbox). 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` | VM 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}") ``` --- # Tenant Branding API URL: https://miosa.ai/docs/api-reference/tenant-branding Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/tenant-branding Source: src/routes/docs/api-reference/tenant-branding/+page.md Description: Customize the logo, name, and colors shown on sandbox 404 and 502 error pages. The branding API controls the appearance of proxy-generated error pages (404, 502, gateway timeout) served within your custom preview domain. Set your logo, product name, support URL, and primary accent color so end-users see your brand - not MIOSA's. Base path: `/api/v1/tenant/branding` Branding changes are served immediately from cache. There is no propagation delay. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/tenant/branding` | Get current branding | | `PUT` | `/api/v1/tenant/branding` | Set or replace branding | | `DELETE` | `/api/v1/tenant/branding` | Reset to MIOSA defaults | --- ## Get Branding **`GET /api/v1/tenant/branding`** ### Auth ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` ```json { "branding": { "logo_url": "https://cdn.cliniciq.com/logo.png", "name": "ClinicIQ", "support_url": "https://cliniciq.com/support", "primary_color": "#0066FF" } } ``` Returns `{"branding": null}` if no custom branding has been set. --- ## Set Branding **`PUT /api/v1/tenant/branding`** Replaces all branding fields. Omitted fields are cleared to their defaults. ### Request Body Wrap fields under a `"branding"` key. | Field | Type | Required | Description | |-------|------|----------|-------------| | `logo_url` | string | No | HTTPS URL to your logo image | | `name` | string | No | Product or company name | | `support_url` | string | No | Link shown on error pages for user support | | `primary_color` | string | No | Hex color for CTA buttons (e.g. `"#0066FF"`) | ```json { "branding": { "logo_url": "https://cdn.cliniciq.com/logo.png", "name": "ClinicIQ", "support_url": "https://cliniciq.com/support", "primary_color": "#0066FF" } } ``` ### Response - `200 OK` ```json { "branding": { "logo_url": "https://cdn.cliniciq.com/logo.png", "name": "ClinicIQ", "support_url": "https://cliniciq.com/support", "primary_color": "#0066FF" } } ``` --- ## Delete Branding **`DELETE /api/v1/tenant/branding`** Resets all branding to MIOSA defaults. Error pages will show the MIOSA logo and color scheme. ### Response - `200 OK` ```json { "branding": null } ``` --- ## Errors | Status | Code | Cause | |--------|------|-------| | 404 | `tenant not found` | Tenant does not exist | | 422 | validation error | A field failed validation (e.g. invalid URL, invalid hex color) | --- ## Examples ```bash # Set branding curl -X PUT https://api.miosa.ai/api/v1/tenant/branding \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{ "branding": { "logo_url": "https://cdn.cliniciq.com/logo.png", "name": "ClinicIQ", "support_url": "https://cliniciq.com/support", "primary_color": "#0066FF" } }' # Get curl https://api.miosa.ai/api/v1/tenant/branding \ -H "Authorization: Bearer msk_u_..." # Reset curl -X DELETE https://api.miosa.ai/api/v1/tenant/branding \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() client.tenant.set_branding( logo_url="https://cdn.cliniciq.com/logo.png", name="ClinicIQ", support_url="https://cliniciq.com/support", primary_color="#0066FF", ) branding = client.tenant.get_branding() print(branding.name) ``` ```typescript const client = new Miosa(); await client.tenant.setBranding({ logoUrl: 'https://cdn.cliniciq.com/logo.png', name: 'ClinicIQ', supportUrl: 'https://cliniciq.com/support', primaryColor: '#0066FF', }); const branding = await client.tenant.getBranding(); console.log(branding?.name); ``` --- # Tenant Events (SSE) API URL: https://miosa.ai/docs/api-reference/tenant-events Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/tenant-events Source: src/routes/docs/api-reference/tenant-events/+page.md Description: Subscribe to a real-time server-sent event stream of all tenant-wide platform events. The tenant events stream delivers real-time Server-Sent Events (SSE) for all activity across your tenant - sandbox lifecycle changes, deployments, webhook deliveries, and more. Subscribe once and receive events for all your resources. Base path: `/api/v1/events/stream` This endpoint uses `text/event-stream` (SSE). Set `Accept: text/event-stream` on your request, or use a native `EventSource` client. The `Authorization` header cannot be set by browser `EventSource`. Use a server-side client or pass an SSE ticket obtained from `POST /api/v1/auth/sse-ticket` for browser usage. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/events/stream` | Open tenant-wide SSE event stream | --- ## Open Event Stream **`GET /api/v1/events/stream`** Upgrades to an SSE stream. The connection stays open until the client disconnects or the server closes it. A `: heartbeat` comment is sent every 15 seconds to keep the connection alive through proxies. ### Auth ``` Authorization: Bearer msk_... ``` ### Query Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `types` | string | No | Comma-separated glob patterns to filter events (e.g. `sandbox.*,webhook.delivered`) | ### Response - `200 OK` (`text/event-stream`) Each event is a standard SSE frame: ``` id: evt_01hwxyz... event: sandbox.ready data: {"type":"sandbox.ready","id":"sbx_abc123","status":"running","tenant_id":"...","ts":"2026-05-26T10:14:23Z"} id: evt_01hwyza... event: deployment.succeeded data: {"type":"deployment.succeeded","id":"dep_xyz456","url":"https://my-app.miosa.app","ts":"2026-05-26T10:15:01Z"} : heartbeat ``` ### Event Shape All events share a common envelope: ```json { "type": "sandbox.ready", "id": "sbx_abc123", "tenant_id": "ten_...", "ts": "2026-05-26T10:14:23Z" } ``` Additional fields vary by event type (see table below). --- ## Event Types ### Sandbox Events | Type | Description | Extra Fields | |------|-------------|-------------| | `sandbox.created` | Sandbox provisioned | `image`, `workspace_id` | | `sandbox.ready` | Sandbox reached `running` status | `status` | | `sandbox.error` | Sandbox failed to start | `error`, `reason` | | `sandbox.destroyed` | Sandbox deleted | - | | `sandbox.paused` | Sandbox vCPUs suspended | - | | `sandbox.resumed` | Sandbox vCPUs resumed | - | ### Computer Events | Type | Description | Extra Fields | |------|-------------|-------------| | `computer.created` | Computer provisioned | `size`, `workspace_id` | | `computer.running` | Computer reached `running` status | - | | `computer.stopped` | Computer stopped | - | | `computer.deleted` | Computer deleted | - | ### Deployment Events | Type | Description | Extra Fields | |------|-------------|-------------| | `deployment.build_started` | Build job enqueued | `version_id` | | `deployment.succeeded` | Build completed and promoted | `url`, `version_id` | | `deployment.failed` | Build or promotion failed | `error` | | `deployment.rollback` | Version rolled back | `from_version`, `to_version` | ### Webhook Events | Type | Description | Extra Fields | |------|-------------|-------------| | `webhook.delivered` | Outgoing webhook POST succeeded | `webhook_id`, `status_code` | | `webhook.failed` | Outgoing webhook POST failed | `webhook_id`, `attempt`, `error` | ### Filtering with `types` Pass a comma-separated list of glob patterns. The `*` wildcard matches any suffix segment: ``` ?types=sandbox.* # all sandbox events ?types=sandbox.*,computer.running ?types=deployment.succeeded,deployment.failed ``` An empty `types` parameter (or no parameter) delivers all events. --- ## Reconnection On disconnect, reconnect using the `Last-Event-ID` header set to the last `id` you received. The server will resume from that cursor, replaying up to 60 seconds of backlogged events. ``` GET /api/v1/events/stream Authorization: Bearer msk_... Last-Event-ID: evt_01hwxyz... ``` --- ## Examples ```bash # All events curl -N -H "Authorization: Bearer msk_u_..." \ -H "Accept: text/event-stream" \ "https://api.miosa.ai/api/v1/events/stream" # Sandbox events only curl -N -H "Authorization: Bearer msk_u_..." \ -H "Accept: text/event-stream" \ "https://api.miosa.ai/api/v1/events/stream?types=sandbox.*" ``` ```python headers = { "Authorization": "Bearer msk_u_...", "Accept": "text/event-stream", } with httpx.stream("GET", "https://api.miosa.ai/api/v1/events/stream", params={"types": "sandbox.*"}, headers=headers) as r: for line in r.iter_lines(): if line.startswith("data:"): event = json.loads(line[5:].strip()) print(event["type"], event["id"]) ``` ```typescript // Server-side (Node.js / Deno) - EventSource doesn't support custom headers in browsers const es = new EventSource( 'https://api.miosa.ai/api/v1/events/stream?types=sandbox.*', { headers: { Authorization: 'Bearer msk_u_...' } } ); es.addEventListener('sandbox.ready', (e) => { const data = JSON.parse(e.data); console.log('Sandbox ready:', data.id); }); es.addEventListener('sandbox.error', (e) => { const data = JSON.parse(e.data); console.error('Sandbox failed:', data.id, data.error); }); es.onerror = () => es.close(); ``` --- # Tenant Preview Domain API URL: https://miosa.ai/docs/api-reference/tenant-preview-domain Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/tenant-preview-domain Source: src/routes/docs/api-reference/tenant-preview-domain/+page.md Description: Configure a custom apex domain for sandbox preview URLs (white-label). The preview domain API lets you map your own apex domain (e.g. `cliniciq.com`) to sandbox preview URLs. When configured, sandboxes created under your tenant will be accessible at `{sandbox-slug}.{your-domain}` instead of the default `*.sandbox.miosa.ai`. **Auth note:** These endpoints accept both `msk_*` API keys and JWT tokens. This matters for white-label integrations where you programmatically manage the domain from a backend service. Only apex domains are accepted (e.g. `cliniciq.com`, not `app.cliniciq.com` or `https://cliniciq.com`). Wildcard and subdomain inputs are rejected with `422 invalid_domain`. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/tenant/preview-domain` | Get current domain and verification status | | `PUT` | `/api/v1/tenant/preview-domain` | Set or replace the preview domain | | `POST` | `/api/v1/tenant/preview-domain/verify` | Recheck DNS verification | | `DELETE` | `/api/v1/tenant/preview-domain` | Remove the custom domain | --- ## Get Preview Domain **`GET /api/v1/tenant/preview-domain`** Returns the configured domain and its DNS verification status. ### Auth ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` (domain configured) ```json { "preview_domain": "cliniciq.com", "status": "configured", "dns_instructions": { "type": "CNAME", "name": "*.cliniciq.com", "value": "proxy.miosa.ai" } } ``` ### Response - `200 OK` (not configured) ```json { "preview_domain": null, "default_domain": "miosa.ai", "status": "not_configured", "dns_instructions": null } ``` | Field | Type | Description | |-------|------|-------------| | `preview_domain` | string\|null | Configured apex domain, or `null` | | `default_domain` | string | Fallback domain used when no custom domain is set | | `status` | string | `"not_configured"`, `"pending_dns"`, or `"configured"` | | `dns_instructions` | object\|null | CNAME record to add at your DNS registrar | --- ## Set Preview Domain **`PUT /api/v1/tenant/preview-domain`** Sets or replaces the custom preview domain. DNS verification is asynchronous - status transitions from `pending_dns` to `configured` once the CNAME resolves correctly. ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `preview_domain` | string | Yes | Apex domain (e.g. `"cliniciq.com"`) | ```json { "preview_domain": "cliniciq.com" } ``` ### Response - `200 OK` ```json { "preview_domain": "cliniciq.com", "status": "pending_dns", "dns_status": "pending", "dns_instructions": { "type": "CNAME", "name": "*.cliniciq.com", "value": "proxy.miosa.ai" } } ``` ### DNS Setup Add a wildcard CNAME at your registrar: | Type | Name | Value | |------|------|-------| | `CNAME` | `*` (or `*.cliniciq.com`) | `proxy.miosa.ai` | --- ## Verify DNS **`POST /api/v1/tenant/preview-domain/verify`** Triggers an immediate DNS re-check. Returns the current verification status without waiting for the background poller. ### Response - `200 OK` ```json { "preview_domain": "cliniciq.com", "status": "configured", "dns_status": "verified" } ``` --- ## Delete Preview Domain **`DELETE /api/v1/tenant/preview-domain`** Removes the custom domain. Sandbox preview URLs revert to `*.sandbox.miosa.ai`. ### Response - `200 OK` ```json { "preview_domain": null, "status": "removed" } ``` --- ## Errors | Status | Code | Cause | |--------|------|-------| | 400 | `preview_domain is required` | Missing body field | | 422 | `invalid_domain` | Protocol prefix, wildcard, path, or subdomain detected | | 404 | `tenant not found` | Tenant does not exist | --- ## Examples ```bash # Set curl -X PUT https://api.miosa.ai/api/v1/tenant/preview-domain \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{"preview_domain": "cliniciq.com"}' # Verify curl -X POST https://api.miosa.ai/api/v1/tenant/preview-domain/verify \ -H "Authorization: Bearer msk_u_..." # Get curl https://api.miosa.ai/api/v1/tenant/preview-domain \ -H "Authorization: Bearer msk_u_..." # Delete curl -X DELETE https://api.miosa.ai/api/v1/tenant/preview-domain \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Set result = client.tenant.set_preview_domain("cliniciq.com") print(result.dns_instructions) # Poll until verified while True: domain = client.tenant.get_preview_domain() if domain.status == "configured": break time.sleep(30) client.tenant.verify_preview_domain() ``` ```typescript const client = new Miosa(); // Set const result = await client.tenant.setPreviewDomain('cliniciq.com'); console.log(result.dnsInstructions); // Get const domain = await client.tenant.getPreviewDomain(); console.log(domain.status); // "pending_dns" | "configured" // Force re-check await client.tenant.verifyPreviewDomain(); // Remove await client.tenant.deletePreviewDomain(); ``` --- # Usage Rollup API URL: https://miosa.ai/docs/api-reference/usage-rollup Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/usage-rollup Source: src/routes/docs/api-reference/usage-rollup/+page.md Description: Aggregate sandbox compute usage and spend by external user, project, or workspace. The usage rollup endpoint aggregates metered usage across sandboxes and computers, grouped by an identifier of your choice. Use it to build per-user billing dashboards, enforce spend policies, or attribute costs to projects. Base path: `/api/v1/usage` Results are limited to 1000 rows per request. For high-cardinality tenants, narrow the period or use a more specific `group_by`. --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/usage` | Aggregate usage for a period | --- ## Get Usage Rollup **`GET /api/v1/usage`** ### Auth ``` Authorization: Bearer msk_... ``` ### Query Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `group_by` | string | No | `external_user_id` | Dimension to aggregate by | | `period` | string | No | `30d` | Predefined period or `custom` | | `start` | string | No | - | ISO 8601 start (required when `period=custom`) | | `end` | string | No | - | ISO 8601 end (required when `period=custom`) | **`group_by` values:** | Value | Description | |-------|-------------| | `external_user_id` | Group by the `external_user_id` you set on sandboxes | | `external_project_id` | Group by `external_project_id` | | `workspace_id` | Group by MIOSA workspace | **`period` values:** | Value | Description | |-------|-------------| | `7d` | Last 7 days | | `30d` | Last 30 days | | `mtd` | Month-to-date (calendar month, resets on the 1st) | | `custom` | Arbitrary range - requires `start` and `end` | ### Response - `200 OK` ```json { "period_start": "2026-04-26T00:00:00Z", "period_end": "2026-05-26T00:00:00Z", "results": [ { "external_user_id": "user_clinic_42", "sandbox_seconds": 14400, "computer_seconds": 0, "storage_gb_hours": 2.5, "credit_cents": 312 }, { "external_user_id": "user_clinic_99", "sandbox_seconds": 3600, "computer_seconds": 7200, "storage_gb_hours": 0.0, "credit_cents": 108 } ] } ``` | Field | Type | Description | |-------|------|-------------| | `period_start` | string | ISO 8601 start of aggregation window | | `period_end` | string | ISO 8601 end of aggregation window | | `results[].{group_by}` | string | Value of the grouping dimension | | `results[].sandbox_seconds` | number | vCPU-seconds used by sandboxes | | `results[].computer_seconds` | number | vCPU-seconds used by computers | | `results[].storage_gb_hours` | number | GiB-hours of attached storage | | `results[].credit_cents` | number | Total cost in platform credit cents | Results are ordered by `credit_cents` descending. --- ## Errors | Status | Code | Cause | |--------|------|-------| | 422 | `invalid group_by` | `group_by` is not one of the accepted values | | 422 | `invalid period` | `period` is not one of the accepted values | | 422 | `invalid_custom_range` | `start` or `end` is missing or not valid ISO 8601 when `period=custom` | --- ## Examples ```bash # 30-day rollup by user curl "https://api.miosa.ai/api/v1/usage?group_by=external_user_id&period=30d" \ -H "Authorization: Bearer msk_u_..." # Custom range curl "https://api.miosa.ai/api/v1/usage?group_by=external_user_id&period=custom&start=2026-05-01T00:00:00Z&end=2026-05-26T00:00:00Z" \ -H "Authorization: Bearer msk_u_..." # Group by project curl "https://api.miosa.ai/api/v1/usage?group_by=external_project_id&period=mtd" \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() # Monthly rollup per user results = client.usage.rollup( group_by="external_user_id", period="30d", ) for row in results: print(f"{row.external_user_id}: {row.credit_cents / 100:.2f} USD") ``` ```typescript const client = new Miosa(); const { results } = await client.usage.rollup({ groupBy: 'external_user_id', period: '30d', }); for (const row of results) { console.log(`${row.externalUserId}: $${(row.creditCents / 100).toFixed(2)}`); } ``` --- # 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 --- # Webhooks API URL: https://miosa.ai/docs/api-reference/webhooks Fallback URL: https://miosa.roberto-c49.workers.dev/docs/api-reference/webhooks Source: src/routes/docs/api-reference/webhooks/+page.md Description: Subscribe to platform lifecycle events via outgoing HTTP callbacks with HMAC signature verification. Webhooks fire outgoing HTTP POST requests to your endpoint when platform events occur - sandbox lifecycle changes, deployment completions, billing events, and more. Each delivery is signed with HMAC-SHA256 so you can verify authenticity. Base path: `/api/v1/webhooks` The webhook `secret` is returned **only once** - in the response to `POST /webhooks`. Store it immediately in a secure location. It cannot be retrieved later (only a masked preview is shown afterward). --- ## Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/webhooks` | List all webhooks | | `POST` | `/api/v1/webhooks` | Create a webhook | | `GET` | `/api/v1/webhooks/{id}` | Get a webhook | | `PATCH` | `/api/v1/webhooks/{id}` | Update a webhook | | `DELETE` | `/api/v1/webhooks/{id}` | Delete a webhook | | `POST` | `/api/v1/webhooks/{id}/test` | Send a test delivery | | `GET` | `/api/v1/webhooks/{id}/deliveries` | List delivery attempts | --- ## List Webhooks **`GET /api/v1/webhooks`** ### Auth ``` Authorization: Bearer msk_... ``` ### Response - `200 OK` ```json { "data": [ { "id": "wh_01hwxyz...", "name": "My Webhook", "url": "https://api.myapp.com/hooks/miosa", "events": ["sandbox.created", "sandbox.destroyed"], "secret_preview": "sk_live_••••••••abcd", "retry_count": 3, "created_at": "2026-05-01T00:00:00Z", "updated_at": "2026-05-01T00:00:00Z" } ] } ``` --- ## Create a Webhook **`POST /api/v1/webhooks`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `url` | string | Yes | HTTPS endpoint to POST events to | | `name` | string | No | Human-readable label | | `events` | string[] | No | Event type filter; empty = all events | | `headers` | object | No | Extra headers to include in deliveries | | `retry_count` | integer | No | Number of retries on failure (0-10, default `3`) | | `metadata` | object | No | Arbitrary key-value metadata | ```json { "url": "https://api.myapp.com/hooks/miosa", "name": "Sandbox lifecycle", "events": ["sandbox.created", "sandbox.ready", "sandbox.error", "sandbox.destroyed"], "retry_count": 3 } ``` ### Response - `201 Created` ```json { "data": { "id": "wh_01hwxyz...", "name": "Sandbox lifecycle", "url": "https://api.myapp.com/hooks/miosa", "events": ["sandbox.created", "sandbox.ready", "sandbox.error", "sandbox.destroyed"], "secret": "sk_live_abc123def456...", "secret_preview": "sk_live_••••••••3456", "retry_count": 3, "created_at": "2026-05-26T10:14:23Z", "updated_at": "2026-05-26T10:14:23Z" } } ``` The `secret` field appears only in this response. Save it securely. --- ## Update a Webhook **`PATCH /api/v1/webhooks/{id}`** Partial update - include only fields you want to change. ### Request Body Same fields as create, all optional. ### Response - `200 OK` Webhook object without the `secret` field. --- ## Delete a Webhook **`DELETE /api/v1/webhooks/{id}`** ### Response - `204 No Content` --- ## Send a Test Delivery **`POST /api/v1/webhooks/{id}/test`** Sends a synthetic `webhook.test` event to the configured URL to verify connectivity and signature verification. ### Response - `200 OK` ```json { "delivered": true, "status_code": 200, "duration_ms": 143 } ``` --- ## List Deliveries **`GET /api/v1/webhooks/{id}/deliveries`** Returns recent delivery attempts with status codes and timing. ### Response - `200 OK` ```json { "data": [ { "id": "del_01hwyza...", "event_type": "sandbox.ready", "status": "delivered", "status_code": 200, "attempt": 1, "duration_ms": 98, "delivered_at": "2026-05-26T10:14:24Z" }, { "id": "del_01hwyzb...", "event_type": "sandbox.created", "status": "failed", "status_code": 500, "attempt": 3, "duration_ms": 5012, "delivered_at": "2026-05-26T10:14:20Z" } ] } ``` --- ## Signature Verification Every delivery includes an `X-Miosa-Signature` header. Verify it on your server before trusting the payload: ``` X-Miosa-Signature: sha256= ``` Compute the expected signature: ``` hmac = HMAC-SHA256(secret, raw_request_body_bytes) expected = "sha256=" + hex(hmac) assert constant_time_equals(expected, request.headers["X-Miosa-Signature"]) ``` Always compare signatures using a constant-time function to prevent timing attacks. Never use `==` for string comparison. ### Example verification (Python) ```python def verify_signature(secret: str, body: bytes, header: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, header) ``` ### Example verification (TypeScript) ```typescript function verifySignature(secret: string, body: Buffer, header: string): boolean { const expected = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex'); return timingSafeEqual(Buffer.from(expected), Buffer.from(header)); } ``` --- ## Retry Policy Failed deliveries are retried with exponential backoff: | Attempt | Delay | |---------|-------| | 2 | 30 seconds | | 3 | 5 minutes | | 4+ | Up to `retry_count` × 10 minutes (capped at 6 hours) | A delivery is considered failed if your endpoint returns a non-2xx status code or does not respond within 30 seconds. Set `retry_count: 0` to disable retries. --- ## Subscribed Event Types | Event | Description | |-------|-------------| | `sandbox.created` | Sandbox created | | `sandbox.ready` | Sandbox reached `running` | | `sandbox.error` | Sandbox failed to start | | `sandbox.destroyed` | Sandbox deleted | | `computer.created` | Computer created | | `computer.running` | Computer reached `running` | | `computer.stopped` | Computer stopped | | `computer.deleted` | Computer deleted | | `deployment.succeeded` | Build succeeded and promoted | | `deployment.failed` | Build or promotion failed | | `deployment.rollback` | Rollback triggered | | `billing.credit_low` | Credit balance below threshold | | `billing.credit_exhausted` | Credit balance reached zero | | `webhook.test` | Synthetic test delivery | Subscribe to all events by omitting the `events` field. Subscribe to a subset by listing specific event types. --- ## Examples ```bash # Create curl -X POST https://api.miosa.ai/api/v1/webhooks \ -H "Authorization: Bearer msk_u_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://api.myapp.com/hooks/miosa", "events": ["sandbox.created", "sandbox.destroyed"] }' # Test curl -X POST https://api.miosa.ai/api/v1/webhooks/wh_01hwxyz/test \ -H "Authorization: Bearer msk_u_..." # Deliveries curl https://api.miosa.ai/api/v1/webhooks/wh_01hwxyz/deliveries \ -H "Authorization: Bearer msk_u_..." ``` ```python client = miosa.Miosa() hook = client.webhooks.create( url="https://api.myapp.com/hooks/miosa", events=["sandbox.created", "sandbox.destroyed"], retry_count=3, ) # Save hook.secret securely - only available now print(hook.secret) # Test result = client.webhooks.test(hook.id) print(result.delivered, result.status_code) ``` ```typescript const client = new Miosa(); const hook = await client.webhooks.create({ url: 'https://api.myapp.com/hooks/miosa', events: ['sandbox.created', 'sandbox.ready', 'sandbox.destroyed'], retryCount: 3, }); // Store hook.secret in your secrets manager - only returned once console.log(hook.secret); // Test connectivity const result = await client.webhooks.test(hook.id); console.log(result.delivered, result.statusCode); ``` --- # 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_* workspace key] -->|server-side only| API[MIOSA API calls] MSK -->|mints via POST /api/v1/browser-tokens| BT[Browser token - scoped + short-lived] BT -->|browser only| EP[Specific sandbox endpoint] MSK -->|mints via POST /api/v1/auth/sse-ticket| ST[SSE ticket - single use] ST -->|EventSource| SS[SSE stream] MSK -->|mints via POST /api/v1/sandboxes/:id/preview-token| MP[mp_ Preview token] MP -->|iframe embed - no msk_ exposed| Preview[MiosaPreview component] MSK -->|mints via POST /api/v1/sandboxes/:id/shares| MS[ms_ Share token] MS -->|URL param - public read-only| ShareURL[Public share URL] ## 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 | The public self-service endpoint creates normal API-purpose keys. Specialized operator or intelligence credentials use separate controlled flows. ### 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" }' ``` ```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": 600, "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. Users can mint personal `user` keys. Tenant owners and administrators can also mint `platform` keys for backend automation. Admin keys remain operator-internal and are not created through the public self-service endpoint. ### 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_*` keys unlock operator-admin endpoints. `msk_p_*` keys are trusted tenant automation credentials, but they do not automatically make a caller a MIOSA operator. User-role callers hitting an admin endpoint receive `403 Forbidden`. Admin endpoints are operator-internal and not documented publicly. ## Preview tokens Preview tokens (`mp_`) are short-lived signed tokens for embedding a sandbox preview in an ` ``` MIOSA handles pixel streaming and input forwarding inside the iframe. The user clicks or types in the iframe and those events go to the desktop. ## What's streamed - **Pixels** - encoded video 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: Use your own application permissions to decide whether to render an interactive iframe or a watch-only surface. Public read-only stream token controls are plan-dependent and should not be assumed unless enabled for your tenant. ## Bandwidth Default codec / bitrate is tuned for typical desktop content, usually about 500 Kbps to 2 Mbps. For high-motion content, such as 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 Stream URLs are short-lived. The default lifetime is 1 hour, and max lifetime is plan-dependent. When a token expires, the stream disconnects. Your frontend should watch `expires_at` and re-mint before that. Multiple stream URLs 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 a branded stream domain so end users see your brand in the iframe URL. The DNS and TLS flow follows the same contract as [Deployment Domains](/docs/deploy/domains/), with a different routing target. Contact support to set up branded streaming. ## 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 stream URL issuance emits an audit event with: - The computer ID - The token prefix - Issuing API key - External attribution If a token is leaked, rotate the user session or stop the Computer while you investigate. Stream tokens are short-lived by default. ## 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 --- # Fast start & readiness URL: https://miosa.ai/docs/computers/fast-start Fallback URL: https://miosa.roberto-c49.workers.dev/docs/computers/fast-start Source: src/routes/docs/computers/fast-start/+page.md Description: How MIOSA starts managed desktop Computers quickly, defines browser readiness, and exposes failures without false Live states. # Fast start & readiness MIOSA Computers use ready capacity on the managed fleet so a new desktop can be assigned without waiting for a full cold boot. The product target is simple: click **Launch**, then receive an interactive desktop in about 800 to 900 ms on the warm path. ## Verified result The production canary measured on 2026-08-06 completed **10 of 10** launches and reached a browser-ready desktop in a median **740 ms**. | Phase | Time | |---|---:| | Successful launches | `10 / 10` | | Create response median | `348 ms` | | Create request to browser-ready desktop median | **`740 ms`** | | Create request to browser-ready desktop p90 | **`809 ms`** | | Slowest sample | `942 ms` | Every canary used the warm path and did not require the restart fallback. The browser used the bundled direct noVNC client, and `Live` required its real RFB connection event. The launch path warmed the future viewer transport concurrently with the create request and retained bounded retry for route-registration races. The 10-sample result is a dated production canary, not a universal latency guarantee. Region, fleet capacity, network latency, image choice, and cold-path fallback can change startup time. ## What browser-ready means MIOSA does not call a Computer ready merely because its database record exists or its lifecycle says `Running`. Browser readiness requires the complete viewing path to work. 1. The create request claims ready desktop capacity. 2. The stream API returns a short-lived authenticated viewer URL. 3. The bundled viewer client connects directly to the authenticated VNC WebSocket. 4. The VNC WebSocket upgrades with the stream token. 5. The browser renders a usable desktop frame. 6. Opening the Computer workspace keeps the desktop mounted and connects its terminal. The managed fleet keeps separate reserves on every host: 20 ready desktop slots and 20 ready sandbox slots. Desktop slots enter the ready pool only after the default MIOSA wallpaper is visible. Tenant wallpaper and desktop branding are applied during claim, before the Computer is handed to the user. A browser error document or failed iframe navigation must not be labeled as a live desktop. The stream URL is short-lived and scoped to the selected Computer. The gallery-to-workspace transition preserves the viewer handoff instead of requesting a conflicting session. ## Application integration Request the Computer stream URL from your backend and pass only the short-lived URL to the browser. Embed the authenticated viewer URL returned by the API instead of rebuilding the desktop URL from the Computer slug. Your UI should represent these states separately: | State | Meaning | Recommended UI | |---|---|---| | `provisioning` | Capacity is being assigned or the guest is becoming ready | Show bounded progress | | `running` with viewer ready | Desktop and stream path are usable | Show `Live` and reveal the viewer | | `paused` | Durable state exists but the active desktop is stopped | Offer Resume | | viewer unavailable | Lifecycle and viewer reachability disagree | Show an actionable error and retry control | | failed | Provisioning or startup failed | Show the backend error and recovery action | Do not promote an iframe to `Live` from its generic `load` event alone. Browsers also fire that event for their own network-error documents. ## Troubleshooting ### The card says Running but the viewer says Host unavailable Treat viewer reachability as authoritative for the user-visible state. Refresh the Computer lifecycle, then retry the stream request. If the mismatch persists, contact support with the Computer ID, region, and approximate creation time. ### The viewer page returns 503 The public viewer gateway cannot reach the active compute proxy or assigned host. This is a platform routing failure rather than proof that your files or Computer record were lost. Retry once, then contact support if it persists. ### The stream request returns 409 The Computer is not currently in a streamable lifecycle state. Refresh its status and resume it when the API reports `paused`. Do not retry indefinitely without re-reading the lifecycle. ### The iframe loads but no desktop appears Confirm that you embedded the authenticated viewer URL returned by the API. Do not substitute a raw desktop entry URL or wait for an undocumented cross-frame event. ## Related documentation - [Computers overview](/docs/computers/overview/) - [Embedding & Streaming](/docs/computers/embedding/) - [Desktop Control](/docs/computers/desktop/) - [Computers API](/docs/api-reference/computers/) --- # 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: Durable cloud desktops for browser agents, human takeover, GUI automation, and long-running computer-use workflows. # Computers A **Computer** is a durable cloud desktop. It gives each user, agent, or workflow a real Linux GUI with browser state, files, shell access, desktop streaming, and programmatic control. Use it when the work depends on pixels, sessions, browser logins, desktop apps, accessibility trees, or a human taking over the same machine an agent was using. For pure code execution, builds, and preview servers, [Sandboxes](/docs/develop/sandboxes/) are cheaper and faster. For a 24/7 production web app, use [App Engine](/docs/deploy/docker-deploy/). If the user or agent needs to see and operate a real screen, use a Computer. If the workload only needs a shell, filesystem, process, or preview port, use a Sandbox. ## What Computers are for Run a browser session that stays logged in, exposes pixels to agents, and can be resumed by a human. Let an operator open the same live desktop, inspect the state, and continue from where automation stopped. Drive desktop apps with mouse, keyboard, clipboard, windows, accessibility, and shell commands. Keep files, browser profiles, installed tools, and local state across stop/start cycles. ## When to use Computer vs Sandbox | Workload | Pick | Why | |---|---|---| | Run tests, install packages, build code, expose a dev server | **Sandbox** | Faster and cheaper shell-first compute | | Computer-use agent that must click through a website | **Computer** | Needs pixels, browser state, and input events | | Logged-in browser session that must survive across tasks | **Computer** | Browser profile persists with the VM | | Headless Playwright, API tests, CLI workflows | **Sandbox** | No visible desktop needed | | Screenshot-based QA or visual inspection | **Computer** | Uses the rendered screen as truth | | Desktop apps, extensions, file pickers, or manual takeover | **Computer** | Needs a full GUI and streamable session | | Public 24/7 app URL for customers | **App Engine** | Built for durable deployments and custom domains | ## What is in the box - **Xfce desktop** with Firefox and common Linux apps pre-installed. - **Terminal** with `bash`, `python`, and a standard CLI toolchain. - **Persistent storage** so files and browser state survive stop/start cycles through snapshot restore. - **Desktop streaming** so the platform or your app can embed the live screen with short-lived tickets. - **Desktop control API** for screenshots, clicks, typing, scrolling, clipboard, windows, accessibility, shell, and files. - **OSA agent** pre-installed and disabled by default. Activate it when you want autonomous desktop task execution. ## What a Computer is, concretely | Layer | What it gives you | |---|---| | Desktop VM | A real Linux graphical session with browser, files, packages, and user state | | Persistent disk | Durable filesystem and browser profile across stop/start cycles | | Stream gateway | Live desktop pixels and input forwarding for platform UI or embedded apps | | Control API | Screenshots, clicks, typing, scrolling, clipboard, shell, files, windows, and accessibility | | Workspace attribution | Tenant, workspace, user, metadata, and billing ownership attached to the machine | | Human takeover | A signed-in operator can open the same running desktop an agent was using | Computers are not disposable command runners. They are long-lived desktop machines that can be resumed, inspected, embedded, and controlled. ## Fast startup MIOSA keeps ready desktop capacity on the managed fleet so a new Computer can be assigned instead of cold-booted from scratch. The production acceptance test measures the complete user-visible path from creation to the first usable desktop frame. The verified 2026-08-06 canary completed **10 of 10** warm launches and reached a browser-ready desktop in a median **740 ms**: | Phase | Time | |---|---:| | Create response median | `348 ms` | | Create request to browser-ready desktop median | **`740 ms`** | | Create request to browser-ready desktop p90 | **`809 ms`** | | Slowest sample | `942 ms` | This is a dated warm-path canary, not a guarantee that every region and capacity condition will complete inside the sampled range. See [Fast start & readiness](/docs/computers/fast-start/) for the readiness contract, measurement boundary, and troubleshooting guidance. ## Access model There are three ways to enter a Computer desktop. They are intentionally different because they protect different surfaces. | Access path | Who uses it | Authentication | |---|---|---| | Platform desktop button | Signed-in MIOSA users | Normal MIOSA session and workspace permissions | | Embedded stream ticket | Your app backend and frontend | Your backend mints a short-lived URL with the MIOSA API key | | Raw external desktop URL | Emergency or direct viewer access | Viewer password gate | Use [Embedding & Streaming](/docs/computers/embedding/) for your own UI. Use `viewer_password()` / `rotate_viewer_password()` only for raw external desktop viewer links. Authenticated platform desktop links and stream tickets do not need the raw viewer password. Mint stream tickets on your backend. Send only the short-lived stream URL to the browser. Never put a MIOSA API key in frontend code. ## 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 Desktop 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, customer, or workspace identifier. Usage is tracked per workspace in the billing dashboard so you can attribute compute costs to individual customers. ## Operator workflow Most production Computer flows look like this: Diagram: sequenceDiagram participant User as User or Agent participant App as Your App participant API as MIOSA API participant VM as Computer VM participant Stream as Stream Gateway User->>App: Start desktop task App->>API: Create or resume Computer API->>VM: Boot desktop and agent services App->>API: Mint stream ticket API-->>App: Short-lived stream URL App-->>User: Embedded desktop iframe User->>Stream: Clicks, keys, clipboard Stream->>VM: Forward input VM-->>Stream: Pixels Stream-->>User: Live desktop The Computer is the stateful machine. The stream ticket is only temporary access to that machine. If the browser tab closes, the Computer keeps its state until you stop or delete it. ## 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 is required. See the Templates documentation for details. ## How it fits together Diagram: graph LR SDK["Python / TypeScript SDK"] -->|REST| API["MIOSA API"] API --> Desktop["Desktop environment"] Desktop -->|input| Session["Desktop session"] Desktop -->|frame| Stream["Pixel stream"] Stream -->|WebSocket| Browser["Browser iframe"] Your code talks to the MIOSA API. MIOSA routes actions to a desktop command service. The command service 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 Fetch signed Computer URLs from your backend, then pass the desktop URL to a browser iframe: ```python urls = computer.urls() # urls["desktop_url"] is a short-lived desktop stream URL. # Pass this URL to your frontend; do not embed your API key. ``` ```typescript const urls = await computer.urls() // urls.desktop_url is a short-lived desktop stream URL. // ``` Never put `msk_*` in a `