# 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