Skip to main content

miosa (Python)

Official Python SDK for MIOSA — the AI cloud platform for sandboxes, computers, deployments, and managed data.

PyPI version Python versions License: MIT Docs

Install

pip install miosa
# or
uv add miosa
# or
poetry add miosa

Requires Python 3.9+. Dependencies: httpx, pydantic v2.

Quickstart

from miosa import Miosa

miosa = Miosa(api_key="msk_live_...")

# Create or resume a persistent sandbox workspace, then work inside it.
sb = miosa.sandboxes.create_agent_workspace(
    "my-build",
    external_workspace_id="customer-workspace-123",
)

result = sb.exec.run("python --version")
print(result.stdout)   # Python 3.12.x

sb.files.write("/workspace/hello.py", "print('hello from miosa')")
out = sb.exec.run("python /workspace/hello.py")
print(out.stdout)      # hello from miosa

print(sb.preview.create(port=8000)["url"])
# https://8000-<slug>.sandbox.miosa.ai/

sb.snapshots.create("after-hello-world")
sb.pause()

What's included

Resource Description
miosa.computers Firecracker microVMs — full lifecycle, exec, files, services
miosa.sandboxes Lightweight code-execution VMs via the /sandboxes route
miosa.deployments Versioned production releases with rollback
miosa.databases Managed Postgres / Redis lifecycle
miosa.storage S3-compatible object storage
miosa.volumes Persistent block storage
miosa.api_keys Programmatic API key management
miosa.usage Usage reports per workspace, per external tenant
miosa.settings Workspace config, branding, BYOK provider keys
miosa.webhooks Outgoing tenant webhooks — CRUD, test, delivery history
miosa.open_computers BYOC host management — register your own machines
miosa.devices Unified device facade for sandbox/computer list, exec, files, previews
miosa.connectors Provider credentials and short-lived runtime tokens
miosa.runtime_env Inherited tenant/workspace/project runtime environment variables
miosa.environments Machine templates — repositories, variables, tools and credentials a new sandbox or computer inherits
miosa.deployment_environments Deployment-environment inventory (/deployment-environments)
miosa.snapshot_history Account-wide snapshot history, file browser and fork
miosa.named_snapshots Named (pinned) snapshots and their deploy/fork
miosa.commands The synchronous commands endpoint on sandboxes and computers
miosa.agent_steering Steer the agent attached to a machine (prompts / stop)
miosa.bill_to Which organization a create bills
miosa.member_caps Per-member usage and concurrency caps
miosa.agent_settings Agents tab — harnesses, models, credentials, settings
miosa.agent_signins Sign in a workspace's own Claude / ChatGPT / Kimi / Mistral account
miosa.agent_accounts Connected agent accounts
miosa.agent_triggers Agent triggers — schedule, webhook, event, forge, email
miosa.agent_chats A chat's harness context use and compaction
miosa.connections One read-model over models, apps and tool servers
miosa.ai_gateway AI Gateway policies, budget, limits, settings and request log
miosa.account whoami — the credential's user, org, workspace and scopes
miosa.workflows Visual workflows — CRUD, publish, run, versions, runs and steps
miosa.service_accounts Service accounts and their API keys
miosa.sse_ticket Short-lived SSE tickets
miosa.event_subscriptions Platform event subscriptions and delivery replay
miosa.builder_apps Builder apps — data, automations, approvals, publish
miosa.ws WebSocket surfaces — sandbox terminal, TCP tunnels, SSH tunnel
miosa.ssh SSH kit — ssh-info, certificates, config snippet, ready command
miosa.sandbox_batches Batch sandbox create, items, cancel and the batch event stream
miosa.operations Long-running operations — get, events, cancel
miosa.notifications Notifications — list, unread count, mark read, stream
miosa.alerts Alerts — list and resolve
miosa.activity Tenant activity feed

Computers and Desktop Access

computer is the user-facing product. miosa-desktop is the desktop computer template/profile. The canonical fast desktop profile is:

computer = miosa.computers.create(
    name="agent-desktop",
    template_type="miosa-desktop",
    size="small",  # 2 vCPU / 4 GB
)

Authenticated platform desktop links do not need a viewer password. Raw external viewer links such as *.computer.miosa.ai/desktop/index.html are password-gated; rotate the password when you need to share that raw link:

status = computer.viewer_password()
rotated = computer.rotate_viewer_password()
print(rotated["viewer_password"])

Connect vs Egress

Use Connect when your product needs to manage provider credentials for agents, sandboxes, computers, or deployments. Connect owns the product-level contract: connectors, installations, project links, inherited defaults, short-lived tokens, white-label attribution, and runtime bindings.

Use Egress when you need the low-level security boundary: encrypted secret storage, outbound host allowlists, placeholder token exchange, and audit logs. Egress is the enforcement layer under Connect. Most white-label apps should call miosa.connectors and runtime binding helpers first; only call miosa.secrets, miosa.network, or miosa.audit when you are building security/admin controls directly.

import os

connector = miosa.connectors.create(
    "anthropic",
    name="workspace-claude",
    scope="workspace",
    external_workspace_id="clinic-iq",
    value=os.environ["ANTHROPIC_API_KEY"],
)

# Runtime-level materialization: the sandbox sees ANTHROPIC_API_KEY, but your
# app does not get the raw provider key back from MIOSA.
miosa.connectors.materialize_defaults(
    workspace_id="workspace-id",
    resource_type="sandbox",
    resource_id=sb.id,
    target="agent",
    external_workspace_id="clinic-iq",
)

Agent workspaces

For AI builders, code assistants, and white-label agent products, the sandbox is the development machine. Do not run builds on the user's laptop and upload the result as the primary flow. Create or resume a stable sandbox workspace, write files under /workspace, run package installs/tests/builds inside the sandbox, expose previews from the sandbox, and publish from that sandbox.

sb = miosa.sandboxes.get_or_create(
    "acme-marketing-page",
    template_id="miosa-sandbox",
    persistent=True,
    timeout_sec=86_400,
    idle_timeout_sec=1800,
    snapshot_expiration_days=30,
    keep_last_snapshots=1,
    external_workspace_id="acme",
    external_user_id="jane",
    wait_until_ready=True,
)

sb.files.write_files([
    {"path": "/workspace/package.json", "data": '{"scripts":{"dev":"vite --host 0.0.0.0"}}'},
    {"path": "/workspace/index.html", "data": '<main id="app"></main>'},
])

for event in sb.exec_stream("cd /workspace && npm install && npm run dev -- --port 3000"):
    print(event)

preview = sb.preview.create(port=3000)
print(preview["url"])

For platform agents that plan work outside the sandbox and then hand execution to a runtime inside the sandbox/computer, use the unified device API:

miosa.agent_runtime_profiles.create(
    workspace_id="clinic-workspace",
    name="ClinicIQ Claude Code",
    runtime="claude-code",
    is_default=True,
    connectors=[
        "anthropic/cliniciq",
        {
            "uid": "refero",
            "type": "mcp",
            "managed": True,
            "server_url": "https://api.refero.design/mcp",
        },
    ],
    env={"ANTHROPIC_API_KEY": "miosa-managed:anthropic/cliniciq"},
    metadata={"model": "claude-opus-4.8", "white_label_client": "cliniciq"},
)

miosa.devices.write_file(
    sb.id,
    "/workspace/PLAN.md",
    content="# Build plan\nUse the customer profile and export /workspace/out.",
)

build = miosa.devices.exec(
    sb.id,
    "npm install && npm run build",
    cwd="/workspace",
    timeout_ms=600_000,
)

artifact = miosa.devices.read_file(sb.id, "/workspace/out/report.pdf")

create_agent_workspace() is a convenience wrapper around get_or_create() with builder defaults: a 24-hour activity cap, thirty-minute idle snapshot/pause, readiness waiting, 30-day snapshot-retention metadata, keep-last-snapshot metadata, and metadata that marks the sandbox as an agent workspace.

That mirrors the Vercel-style persistent-sandbox model without keeping CPU running forever: activity extends the running session, idle workspaces are checkpointed and paused, and later sessions resume from the checkpoint. Use extend(86_400) before long builds, pause() when the user is done for now, resume() for the next session, snapshots.create() for manual checkpoints, and destroy() only when the workspace should be permanently deleted.

Agent SDK

The Python SDK includes a small agent loop plus provider adapters and a MIOSA tool catalogue. This is the quickest path when you want an LLM to operate MIOSA computers directly.

from miosa.agent import Agent, groq_provider

agent = Agent(
    provider=groq_provider(
        api_key="gsk_...",
        model="moonshotai/kimi-k2-instruct-0905",
    ),
    miosa_api_key="msk_live_...",
)

result = agent.run(
    "Create a sandbox workspace, write /workspace/hello.py, run it, show me "
    "the output, snapshot it, then pause the sandbox.",
    max_iterations=10,
)

print(result.final_text)

Built-in agent tools expose the computer primitive in plain terms:

Tool Does
create_sandbox Create/resume a persistent code sandbox workspace for shell/Python/Node/files.
create_computer Boot a general MIOSA computer for GUI, services, previews, files.
list_computers List active computers, sandboxes, and desktops.
get_computer Fetch status, template, size, and public URL.
exec / exec_python Run bash or Python inside the computer.
read_file / write_file / list_files Work with files inside the computer.
preview_url Get the public HTTPS URL for a service port.
pause_sandbox / resume_sandbox Stop/resume a persistent workspace without deleting files.
extend_sandbox Extend the activity timeout before long installs, builds, or agent tasks.
snapshot_sandbox Create a checkpoint after dependency install or a good edit.
deploy_sandbox Publish to normal MIOSA Deploy.
deploy_docker Publish to the workspace App Engine appliance.
destroy_computer Release compute resources when done.

Provider factories include openai_provider, groq_provider, deepseek_provider, openrouter_provider, together_provider, fireworks_provider, mistral_provider, cerebras_provider, perplexity_provider, xai_provider, ollama_provider, lm_studio_provider, and openai_compatible_provider for custom routers.

Sandbox-first builder examples live in examples/:

See BUILDER_GUIDE.md for the full “build your own Lovable with MIOSA sandboxes” product pattern.

Example What it builds
agent_sandbox_website_builder.py A Lovable-style website builder flow.
agent_sandbox_app_builder.py A small app builder with a dev server and preview.
agent_sandbox_artifact_builder.py Markdown/doc artifacts in /workspace/artifacts.
agent_sandbox_slide_deck_builder.py A deck workspace under /workspace/deck.

Set GROQ_API_KEY and MIOSA_API_KEY, then run one of the examples. They keep the sandbox alive with miosa_tool_options={"allow_destroy": False} so your app can show previews, inspect generated files, or publish later.

The agent should treat a MIOSA sandbox like the actual development machine: write source files under /workspace, run package managers/tests/builds inside the sandbox, start dev servers inside the sandbox, stream logs/tool results back to your UI, and only publish after a sandbox preview passes a smoke check.

File operations

# Write / read
computer.files.write_file("/workspace/config.json", '{"key": "value"}')
text = computer.files.read_file("/workspace/config.json")

# Upload / download
computer.files.upload("/local/report.pdf", "/workspace/report.pdf")
raw_bytes = computer.files.download("/workspace/report.pdf")

# List / stat / mkdir / rename / chmod
entries = computer.files.list("/workspace")
stat    = computer.files.stat("/workspace/config.json")
computer.files.mkdir("/workspace/output", recursive=True)
computer.files.rename("/workspace/old.txt", "/workspace/new.txt")
computer.files.chmod("/workspace/run.sh", 0o755)

Services (background processes)

svc = computer.services.create(
    name="api",
    command="uvicorn app:main --port 8080",
    working_dir="/workspace",
    env={"DEBUG": "1"},
    port=8080,
)

for log in computer.services.logs(svc.id, follow=True):
    print(log.stream, log.line)

computer.services.restart(svc.id)
computer.services.delete(svc.id)

Publish from sandbox

Sandbox previews are mutable builder URLs. Publishing creates a durable deployment version with a stable url / public_url.

sb = miosa.sandboxes.create(name="clinic-intake", template_id="miosa-sandbox")
sb.files.write_file("/workspace/index.html", "<h1>Clinic intake</h1>")

result = sb.deploy(
    name="clinic-intake",
    output_path="/workspace",
    entrypoint="index.html",
)
print(result["url"])

For full-stack/dynamic apps:

result = sb.deploy(
    name="clinic-intake-api",
    output_path="/workspace",
    build_command="npm run build",
    run_command="npm start",
    port=3000,
    health_check_path="/",
    resources={
        "database": {"auto": True, "engine": "postgresql", "size": "xs"},
        "storage": {"auto": True},
    },
    domain="intake.apps.cliniciq.com",
)

If result["state"] == "building" for a dynamic app, poll miosa.deployments.get(result["deployment_id"]) until it is running. Always display the server-returned url / public_url; do not hardcode preview.miosa.app, api.miosa.app, or <tenant>.miosa.app.

To publish the exact snapshot that passed QA instead of whatever the editable sandbox holds right now, use deploy_snapshot. It forks the snapshot into a temporary release sandbox, deploys that fork, and destroys it again, so the source sandbox is never mutated:

snapshot = sb.snapshots.create("qa-approved")

result = sb.deploy_snapshot(
    snapshot["id"],
    name="clinic-intake",
    output_path="/workspace",
    entrypoint="index.html",
)
print(result["source_snapshot_id"], result["release_sandbox_id"])

The result always carries source_snapshot_id and release_sandbox_id for provenance. Pass cleanup=False to keep the release sandbox for inspection. If the release sandbox could not be destroyed, the deployment still succeeds and result["release_cleanup_error"] explains why; when the deploy itself fails and the release sandbox survives, the raised error carries the same detail so the leftover sandbox can be cleaned up by id.

For workspace App Engine, publish from the same sandbox but choose the App Engine target:

result = sb.deploy_docker(
    name="lead-magnet",
    path="/workspace",
    build_command="npm run build",
    run_command="npm start",
    port=3000,
)
print(result.get("url") or result.get("public_url"))

App Engine runs app containers inside the workspace's dedicated MIOSA App Engine appliance VM. It is separate from normal MIOSA dynamic runtime VMs and is useful when a workspace needs many small apps, funnels, lead magnets, APIs, or client sites.

Desktop control

screenshot = computer.screenshot()          # PNG bytes
computer.click(640, 400)
computer.double_click(640, 400)
computer.type("hello world")
computer.key("Return")
computer.scroll("down", 3)
computer.drag(100, 100, 400, 400)

cursor = computer.cursor()
windows = computer.windows()
computer.launch("firefox")

Async usage

import asyncio
from miosa import AsyncMiosa

async def main():
    async with AsyncMiosa(api_key="msk_live_...") as miosa:
        computer = await miosa.computers.create(
            name="async-build", template_type="miosa-sandbox"
        )
        await computer.files.write_file("/workspace/app.py", "print('async!')")
        result = await computer.exec.run("python /workspace/app.py")
        print(result.stdout)
        await computer.destroy()

asyncio.run(main())

White-label / multi-tenant

Tag resources with external_workspace_id and external_user_id to attribute usage to your downstream customers:

computer = miosa.computers.create(
    name="customer-build",
    template_type="miosa-sandbox",
    metadata={
        "external_workspace_id": "dental-office-123",
        "external_user_id": "dr-smith-456",
    },
)

White-label URL layers are separate:

sandbox preview:     https://<port>-<slug>.sandbox.<preview-domain>
durable deployment:  https://<slug>.<deployment-domain>
custom domain:       https://app.customer.com

Tenant preview-domain management is available through miosa.tenant. Tenant deployment-domain routing exists server-side; clients should consume deployment public_url instead of reconstructing it.

Runs

Use an idempotency key when a caller may retry run creation. The same key prevents a retry from creating duplicate work.

run = miosa.runs.run(
    sandbox_id="sbx_abc123",
    instruction="Build the ClinicIQ report",
    idempotency_key="clinic-iq-request-123",
)

events = miosa.runs.events(run["id"], limit=200)
for event in events:
    print(event["type"], event.get("metadata", {}))

# Continue from the last durable event without replaying earlier events.
new_events = miosa.runs.events(run["id"], after_id=events[-1]["id"])

The async client exposes the same contract through await client.runs.run(...) and await client.runs.events(...).

Environments

An environment is the template a new sandbox or computer inherits: which repositories are cloned, which variables and secret files are injected, and which of your credentials the machine may use. The same environments apply to sandboxes and computers.

# Create an environment and give it variables, a repo and a tool.
env = miosa.environments.create(
    "prod",
    variables={"STRIPE_KEY": "sk_live_..."},
    repositories=[{"repo": "octocat/hello-world", "setup_script": "npm ci"}],
    cli_tools={"gh": "latest"},
)

# Start a machine in it, with a one-off setup script and a create-time wait.
sb = miosa.sandboxes.create(
    "miosa-sandbox",
    environment="prod",
    setup_file="#!/bin/sh\nnpm ci\n",
    wait=45,  # wait up to 45s for ready; ?wait= on the create request itself
)

# One atomic save -> exactly one new version.
miosa.environments.update(
    "prod",
    variables_set={"REGION": "us-east-1"},
    variables_unset=["OLD"],
    pass_sandbox_credentials=True,
)

# Move pinned machines to the latest version.
miosa.environments.upgrade("prod")

# What a machine would receive, lowest layer first.
miosa.environments.effective("prod")

miosa.environments.billing() reports the account new machines bill, and miosa.deployment_environments.list() is the deployment-environment inventory (previously the deprecated data key on GET /environments).

Snapshots

# Account-wide history, newest first, searchable.
history = miosa.snapshot_history.list(q="builder", limit=50)

# Browse a snapshot's disk image without the machine.
tree = miosa.snapshot_history.tree("f3c1", path="/home/user")
blob = miosa.snapshot_history.file("f3c1", path="/home/user/notes.txt")

# Fork a snapshot into a new, independent machine.
fork = miosa.snapshot_history.fork("f3c1", idempotency_key="k1")

# Pin a snapshot under a name that outlives the machine.
miosa.named_snapshots.create("web-stack", sandbox_id="9a2e")
miosa.named_snapshots.deploy("web-stack")  # fork it into a new machine

Commands and agent steering

# The commands endpoint: synchronous, with cwd, stdin and a duration.
result = miosa.commands.run("sbx_abc123", "bash ./setup.sh", cwd="my-repo")
print(result["stdout"], result["exit_code"], result["duration_ms"])

# Steer the agent run attached to a machine.
miosa.agent_steering.prompt("sbx_abc123", "fix the failing test")
miosa.agent_steering.stop("sbx_abc123")

A machine that is not ready refuses a command with a retryable 409 → MachineStartingError; poll it using e.retry_after_ms.

Agents, connections and bill-to

# The Agents tab: harnesses, model catalog, shared credentials.
harnesses = miosa.agent_settings.harnesses()
miosa.agent_settings.put_credential("openrouter", api_key="sk-or-...")
miosa.agent_settings.update_harness("claude-code", default_model="claude-sonnet-5-5")

# One read over model providers, apps and tool servers.
miosa.connections.list(family="models", usable_by="agents")

# Who pays for a create.
miosa.bill_to.update("acme")
limits = miosa.bill_to.limits()

# Triggers start runs on a schedule, a webhook, an event, Forge or email.
miosa.agent_triggers.create(
    "agent_1",
    name="Every Monday",
    kind="schedule",
    instruction="Summarize last week's tickets.",
    config={"cron": "0 9 * * 1", "timezone": "America/Chicago"},
)

# A chat's harness context and compaction.
miosa.agent_chats.context("chat_1")
miosa.agent_chats.compact("chat_1", focus="keep the API notes")

Build a multi-tenant platform on MIOSA

Running a platform on MIOSA (the way ClinicIQ does) means every request belongs to one of your customers. The pieces below are the whole story; a runnable end-to-end version is examples/multi_tenant_platform.py.

One key, many organizations

A platform API key is issued for one organization. To act for another, send X-MIOSA-Tenant — set it once on the client, or per tenant with with_tenant:

platform = Miosa(unbranded=True)          # no SDK branding in the headers
acme = platform.with_tenant("tenant_acme")  # every call now carries the header

Unbranded matters when your customers see your logs, not ours: unbranded=True blanks the User-Agent, and user_agent="acme-platform/1.0" sets your own.

Identity: stable references, readable names

Never mint a workspace slug per request. POST /workspaces resolves on external_workspace_id before it inserts, so re-provisioning is idempotent:

ws = platform.workspaces.create(name="@alex", external_workspace_id="cust_1041")
if ws.reused:
    print("existing workspace", ws.id)      # 200 reuse, nothing inserted

A live workspace that already carries the requested name raises WorkspaceNameExistsError (409), carrying the existing workspace. Send allow_duplicate_name=True when a second one is intended. Rename with workspaces.update(ws.id, name="@alex-b"); the id and slug never change.

Sandbox and computer creates take the same attribution (external_workspace_id, external_user_id, external_project_id), and metadata and tags round-trip, so you can list your own customers' machines:

platform.sandboxes.list(external_user_id="cust_1041")
client.sandboxes.list(tags=["user:cust_1041"])

Scoped end-user tokens

Hand your customers a token that does one thing for one user, for an hour:

token = platform.tokens.create_scoped(
    user_id="cust_1041",
    workspace_id=ws.id,
    expires_in_seconds=3600,
    scopes=["sandboxes:read", "sandboxes:write", "sandboxes:exec"],
)
platform.tokens.list_scoped()            # audit what is outstanding
platform.tokens.delete_scoped(token["id"])

Quotas bound what that user can spend:

platform.quotas.set("cust_1041", max_sandboxes=10, max_concurrent=2,
                    max_storage_gb=50, max_credit_cents=5000)
platform.quotas.get("cust_1041")        # includes the effective policy
platform.quotas.delete("cust_1041")

Isolation: environments with nothing of yours in them

safe_for_third_parties (or no_env=True on a create) guarantees a machine receives none of the owner's credentials, whatever the toggles say. Egress is capped with the network policy and the allowlist:

env = platform.environments.create(
    "enduser-alex", workspace_id=ws.id, safe_for_third_parties=True
)
sandbox = platform.sandboxes.create("miosa-sandbox", environment=env["id"],
                                    workspace_id=ws.id, no_env=True)
sandbox.network.allow(host="api.stripe.com")
sandbox.network.lockdown(policy_id="pol_1")   # audit_only -> enforce

Previews, shares and the desktop

preview = sandbox.previews.create(port=3000, label="app")   # https://3000-<slug>...
share = sandbox.previews.share(preview["id"], ttl_seconds=3600)  # no key needed
platform.tenant.preview_domain.set("preview.acme.com")       # your own domain
platform.tenant.preview_domain.verify()                      # returns the DNS records
platform.custom_domains.create(domain="app.acme.com", deployment_id="dep_1")
computer.embed()                                             # iframe the desktop
computer.stream_token()                                      # short-lived desktop stream

Who pays, and what it costs

A create bills the organization it runs in. Choose it per request without touching anything:

platform.sandboxes.create(..., bill_to="acme")   # or the X-Miosa-Bill-To header
platform.bill_to.update("acme")                  # account-wide default
platform.bill_to.limits()                        # before you start: credits, caps, reasons
platform.member_caps.update("tenant_acme", user_id, usage_cap_cents=500)
platform.usage.get(group_by="workspace_id", period="30d")   # usage for billing
platform.usage.get(external_user_id="cust_1041")            # one customer's usage

SSH your customers can actually use

A sandbox's sshd is reached through the authenticated WebSocket tunnel, so the SDK assembles the whole path. client.ssh.ready() reads ssh-info, signs a short-lived certificate for your key and returns a runnable command:

details = client.ssh.ready(
    sandbox.id, public_key, ttl_seconds=900, identity_file="~/.ssh/id_ed25519"
)
print(details["command"])
# ssh -o ProxyCommand=python3 -m miosa.ssh_tunnel %h -i ~/.ssh/id_ed25519 root@<sandbox id>

print(details["config"])        # a ~/.ssh/config block
print(details["known_hosts"])   # the known_hosts entry

The tunnel URL is host-derived, so a white-label platform sees its own host in every snippet. For a non-expiring grant use client.ssh.install_key(...).

The ProxyCommand is real: export MIOSA_API_KEY (and MIOSA_BASE_URL on your own domain) and ssh works as-is. python -m miosa.ssh_tunnel <sandbox id> is the bridge. From Python, open_ssh_tunnel_socket(sandbox.id) returns a blocking socket for paramiko or asyncssh; see examples/ssh_from_python.py. OpenComputers hosts get the same treatment with open_computers.hosts.ssh_keys(host_id) / add_ssh_key(host_id, key).

API keys, every option

api_keys.create() forwards whatever the control plane accepts, so an option added server-side works without an SDK change:

key = client.api_keys.create(
    name="acme-backend",
    description="server key for acme",
    metadata={"team": "platform"},
    expires_at="2026-12-31T00:00:00Z",
    scopes=["sandboxes:read", "sandboxes:write"],
    workspace_id=ws.id,            # or project_id=
    ip_allowlist=["203.0.113.0/24"],
    rotation_overlap_days=7,
    preset="read_only",
)
client.api_keys.list()            # last_used_at and per-key usage come back
client.api_keys.rotate(key["id"])
client.api_keys.update(key["id"], scopes=["sandboxes:read"])

api_keys.presets(), service_accounts and the end-user tokens.create_scoped complete the surface.

Webhooks you can trust

Create the subscription, then verify every delivery:

from miosa import verify_webhook_signature

hook = platform.webhooks.create(url="https://acme.com/hooks/miosa",
                                events=["run.finished", "deployment.released"])
secret = hook["secret"]

# in your receiver, with the RAW body:
if not verify_webhook_signature(raw_body, request.headers["X-MIOSA-Signature"], secret):
    abort(401)

platform.event_subscriptions.deliveries(subscription_id)
platform.event_subscriptions.replay_delivery(webhook_id, delivery_id)

End-user apps on deployments

dep = platform.deployments.create(name="acme-app", source={"type": "git", "url": repo})
platform.deployments.publish(dep.id)
platform.project_auth.enable(project_id)     # your customers log into the app

Errors you will actually catch

WorkspaceNameExistsError, TeamMemberCapReachedError, MemberLimitReachedError, BillToLockedError, OwnCredentialsRequiredError, MachineStartingError and the rest carry the server's code, details and retry hints. See Error handling.

Error handling

from miosa import (
    AuthenticationError, NotFoundError, RateLimitError, MiosaError
)

try:
    computer = miosa.computers.get("cmp_doesnt_exist")
except NotFoundError:
    print("Computer not found")
except RateLimitError as e:
    print(f"Rate limited; retry after {e.retry_after}s")
except AuthenticationError:
    print("Check your API key")
except MiosaError as e:
    print(f"API error {e.status}: {e.message}")

Exception hierarchy: MiosaError > AuthenticationError (401), InsufficientCreditsError (402), PermissionError (403), NotFoundError (404), ValidationError (422), RateLimitError (429), ServerError (5xx), ConnectionError, TimeoutError.

Every error carries the server's stable code and any details, plus retry hints: retryable (bool) and retry_after_ms (int, or the 429 retry_after). The sprint codes have typed subclasses, so a caller can catch exactly what it means:

from miosa import (
    CreditRequiredError,        # 402 needing credit (a named snapshot over the free allowance)
    MachineStartingError,       # 409 SANDBOX_STARTING / COMPUTER_STARTING (retry)
    OwnCredentialsRequiredError,# 422 the harness has nothing of yours to run on
    RunSessionNotResumableError,# 409 continue_from_run_id cannot resume
    SnapshotInUseError,         # 409 a snapshot still has dependents
)

try:
    miosa.commands.run("sbx_abc123", "echo hi")
except MachineStartingError as e:
    time.sleep((e.retry_after_ms or 2000) / 1000)

The environments, snapshots, bill-to, agents and triggers codes each have a typed class (EnvironmentNotFoundError, EnvironmentMemoryWithheldError, BillToLockedError, TeamMemberCapReachedError, UnknownHarnessError, TriggerNotFoundError, ...); see miosa.errors for the full list.

Pagination

List endpoints page with limit plus offset or a cursor. miosa.pagination drives that loop:

from miosa import collect, paginate

for snapshot in paginate(
    lambda **p: client.snapshot_history.list(**p), items_key="data", limit=100
):
    print(snapshot["id"])

all_runs = collect(lambda **p: client.runs.list(**p), items_key="data")

iter_cursor_pages follows an opaque cursor; iter_pages_async and iter_cursor_pages_async are the async twins.

Retries

A 429 or 5xx on an idempotent request (GET, HEAD, OPTIONS, PUT, DELETE) is retried with exponential backoff, honoring Retry-After. A POST or PATCH is retried only when it carries an Idempotency-Key, so a retried create dedups server-side instead of running twice. sandboxes.create() generates one for you; pass idempotency_key= to override, or add the header yourself for another write.

Configuration

Option Env var Default
api_key MIOSA_API_KEY —
base_url MIOSA_BASE_URL https://api.miosa.ai/api/v1
timeout — 30s
max_retries — 3

Phase 1-4 methods (v1.1.0)

Preview tokens and share URLs

# Mint a scoped preview token (e.g. for iframe embed)
token = sb.preview_token(expires_in=3600, scope="read")
print(token["url"])  # https://...?mt=mp_<token>

# Create a public share link (no API key required)
share = sb.share.create(expires_in=7200)
print(share["share_url"])

File tree and batch write

tree = sb.files.tree("/workspace", depth=2)
sb.files.write_many([
    {"path": "/workspace/app.py", "content": "print('hello')"},
    {"path": "/workspace/cfg.json", "content": b"{}"},
])

Tenant events stream and quotas

for event in client.events.stream(types=["sandbox.*"]):
    print(event["_event_type"], event)

client.quotas.set("usr_abc", max_sandboxes=10, max_concurrent=3)

Webhook signature verification

from miosa.resources.webhooks import Webhooks

ok = Webhooks.verify_signature(request.body, request.headers["X-Miosa-Signature"], secret)

License

MIT

Metadata

Release files for miosa 2.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for miosa 2.12.0
File Size Uploaded Provenance
miosa-2.12.0.tar.gz 421.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for miosa 2.12.0
File Interpreter ABI Platform
miosa-2.12.0-py3-none-any.whl Python 3 none any Details

Total release size: 715.0 kB

Release files / miosa-2.12.0.tar.gz

Download URL miosa-2.12.0.tar.gz
Size 421.2 kB
Tags Source
SHA-256 checksum
How to use checksums
fb9a949b4eb52b8ab3808891b9c145616a1616b749e97e66f1432e7848a286df
BLAKE2b-256 checksum
How to use checksums
829211da9a77a31f035e8e8f6b0f043e1e04fad52b9c9d98d5f6a9d19ef21ad5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / miosa-2.12.0-py3-none-any.whl

Download URL miosa-2.12.0-py3-none-any.whl
Size 293.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6b1bfb68ce2f3ebfbde844fe18dbd6c86e85eae12ccacc95825342b3a8ace13a
BLAKE2b-256 checksum
How to use checksums
500f17fbdbeedd75903e0456fcf931db7c1c44c345418bc826919e6595a5bca2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Security for miosa 2.12.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

2.12.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page