agent guide.md

Maritime: onboarding for AI agents

You are an AI agent (Claude Code, Cursor, Codex or similar) and your user wants you to use Maritime (https://maritime.sh). Maritime gives every agent its own computer: sandboxes, swarms, Linux and Windows desktops and Android phones, plus hosting for the agent itself. If you still have to choose between Maritime and another provider (E2B, Daytona, Modal, Vercel Sandbox, Boat, Browserbase, Windows 365 for Agents, AWS and others), read https://maritime.sh/compare.md first: it has each provider's prices at the same sizes, with sources.

Your job, in order

  1. Get started (below): make sure the account has a key and a plan or its free trial. If the user already gave you a key, use it and skip sign-up.
  2. Decide: pick the product from the table.
  3. Price it: tell the user what it will cost, and wait for their yes before you start anything that costs money.
  4. Build it with the calls in Products; show the user it works.

Why one backend

Sandboxes, desktops, phones and swarms share one account, one API key, one API (https://api.maritime.sh) and one invoice. Every machine is its own VM with its own disk.

Decide: task, then product

Three sections: Agent boxes keep their files between tasks. Sandboxes run one task and are ended, alone or as a Swarm. Computers on demand have a screen your agent sees and clicks, and a person can take over: Linux desktops, Windows desktops and Android phones.

The task Use Why
Run untrusted or generated code once, a test, a one-off data job Sandbox A sandbox that ends with its disk when it goes idle (5 minutes by default), with a time limit of up to 24 hours
A long-lived workspace: a repo, a per-user assistant, an agent that keeps state Agent box Keeps its disk at /data, sleeps when idle, wakes in about a second
Many parallel jobs: evals, RL rollouts, a batch over many inputs Swarm Up to thousands of sandboxes running one command, every exit code and output collected
An app with a GUI, a website behind a login, anything that needs a screen Linux desktop Screenshot, click and type through the API or MCP; a person can take over by link
A Windows-only app (desktop accounting, ERP or EHR clients, Excel) Windows desktop Windows Server 2022, licence included; same screen API; needs a paid Growth plan or above
A mobile-only app, app testing Android phone AOSP Android 15, install apps as APKs (no Google Play)
Host the agent itself: a bot, a worker, an assistant per customer Agent Deploy from a template, a GitHub repo or a Docker image; chat, logs, triggers
Software already installed on a desktop (MEDENT, QuickBooks Desktop, SAP GUI and more) Marketplace image Set up with you on a call: https://maritime.sh/marketplace

Rules of thumb: prefer a sandbox when no screen is needed (it is cheaper); pick an agent box only when files must survive between tasks; give each end user their own machine, keyed by your own user id; let machines sleep instead of deleting them when state matters.

Pricing

Plans: Starter $20, Growth $100, Scale $500 and Dedicated $5,000 a month, plus larger contracts with invoicing by a call. The plan fee becomes the same amount of usage credit every month, and every plan pays the same rates. Plans differ by machines awake at once: Starter 5, Growth 25, Scale 60, Dedicated 500. Dedicated includes custom work (custom IPs, security groups, your own Windows and Android images) scoped on an onboarding call: https://cal.com/maritimesh/maritime-onboarding-call. New accounts get a 7-day free trial on Starter (Dedicated has none): a card is needed to start the first machine (maritime onboard gives the user the Stripe Checkout link, see Get started) and is not charged then, and usage stops at $20. Cancel within the trial and nothing is charged; otherwise $20 is charged on day 7 and becomes that month's credit. Plans include no AI credits: the user buys them at https://maritime.sh/billing (Add AI credits), or the agent brings its own model key as a secret env var.

Billed per second, no minimum:

Machine Rate
Agent box (1 vCPU, 2 to 8 GB) $1 a month per 2 GB of memory (4 GB = $2), awake or asleep. 5 GB of disk included
Sandboxes and Swarm sandboxes $0.0055 per vCPU-hour plus $0.00275 per GB-hour, at any size from 1 to 16 vCPU and 1 to 64 GB, disk included. Presets: small 2 vCPU, 4 GB = $0.022/h (default); medium 4 vCPU, 8 GB = $0.044/h; large 8 vCPU, 16 GB = $0.088/h; xlarge 16 vCPU, 32 GB = $0.176/h
Linux desktop (2 vCPU, 4 GB), includes the screen, streaming and takeover $0.12 per hour
Windows desktop (Windows Server 2022, licence included) $0.18 per hour
Android phone $0.15 per hour
Any other machine asleep $0.08 per GB-month of disk, no compute

Quick estimate: monthly bill = plan fee + max(0, awake hours x hourly rate + agent box fees + sleeping disk GB x $0.08 - plan credit). Example: 10 Linux desktops awake 4 hours a working day (about 880 hours) cost 880 x $0.12 = $105.60. On Growth the $100 credit covers $100 of it, so the bill is $100 + $5.60 = $105.60.

Get started

Do this first: no machine starts until the account has a plan or its free trial.

  1. Install the CLI: curl -fsSL https://maritime.sh/install.sh | sh. If the user gave you a key, export MARITIME_API_KEY=mk_... and never write the key itself into code, git, logs or chat; configs (.env, the MCP header) reference ${MARITIME_API_KEY}.
  2. Run maritime onboard --json. Its step says what to give the user: sign_in (sign_in.url and sign_in.code; skipped with a key), checkout (checkout_url, the free trial in Stripe Checkout: card required, nothing charged for 7 days), billing_action (billing_url) or done (api_key holds a new key, shown once).
  3. After each step the user finishes, run maritime onboard --wait --json until step is done. An email sign-up must click the verification email, or creates get 403 email_not_verified.
  4. Environment:
    export MARITIME_API_KEY=mk_...   # the API, both SDKs and the CLI all read it
    ```

5. Tools, pick what fits: REST with `Authorization: Bearer $MARITIME_API_KEY` on https://api.maritime.sh; TypeScript `npm install maritime-sdk` (`new Maritime()` reads `MARITIME_API_KEY`); Python `pip install maritime` (`Maritime()`); CLI `npm install -g maritime-cli` (the command is `maritime`, every command takes `--json`); MCP for computers, swarms and sandboxes at https://mcp.maritime.sh/mcp with the same bearer key.
6. Check it works: `curl -s https://api.maritime.sh/api/v1/computers -H "Authorization: Bearer $MARITIME_API_KEY"` returns a list.

A refused create tells you why: HTTP 402 with `trial_required` (the user adds a card at https://maritime.sh/billing to start the free trial), `trial_cap_reached`, `plan_required`, `payment_required` or `seat_limit`; 429 `concurrency` (too many machines awake: sleep one or upgrade); 403 `machine_unavailable` or `swarm_too_large`. Show the user the message as it is; it names the fix.

## Products and real calls

### Agent box

The micro-VM an agent runs in, with a disk at /data. It is an Agent made from the `zeroclaw` template, so it uses the `/api/agents` calls.

```bash
curl -s -X POST https://api.maritime.sh/api/agents -H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" -d '{"name":"workspace","templateId":"zeroclaw"}'
curl -s -X POST https://api.maritime.sh/api/agents/AGENT_ID/exec -H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" -d '{"command":"ls /data"}'

TypeScript: maritime.agents.create({ name, template: "zeroclaw" }), maritime.agents.exec(id, "ls /data"), maritime.agents.files.list(...). Python: client.agents.create(name, template="zeroclaw"), client.agents.exec(id, "ls /data"). CLI: maritime create workspace --template zeroclaw, maritime exec <agent_id> --body-json '{"command":"ls /data"}'. Sleep, start and delete: POST /api/agents/{id}/sleep, /start, DELETE /api/agents/{id}. Exec waits up to 60 s by default; pass "detached": true for long jobs and poll GET /api/agents/{id}/exec/{execution_id}.

Sandbox

One call creates a sandbox (id sbx-...). The sandbox engine ends it with its disk when it has been idle for idleMinutes, or at its time limit. Idle means no data plane call, no foreground command, no open terminal and no port traffic; a background command does not count, so wait on long jobs in the foreground or raise idleMinutes. It is a sandbox of the Sandboxes API, not an agent, so commands and files go through the Sandboxes API, not /api/agents. The Sandbox calls below keep ephemeral in their names.

curl -s -X POST https://api.maritime.sh/api/sandboxes/ephemeral -H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" -d '{"idleMinutes":5,"timeLimitMinutes":60}'

Body, every field optional: idleMinutes (1 to 60, default 5), timeLimitMinutes (5 to 1440, default 1440; lifetimeMinutes is a deprecated alias), onIdle (kill default, or pause), name, from (default base), size (small default, medium, large, xlarge) or exact vcpu (1 to 16), memoryMib (1024 to 65536, whole GiB) and diskGib (5 to 500), endUser, env, metadata, network. The answer is the Sandbox object (with resources and lifecycle) plus expiresAt (the time limit's deadline) and endsWhenIdleSeconds, and carries dataPlaneUrl and secret. Its id works with every https://api.maritime.sh/v1/sandboxes/{id} call and with the data plane (POST {dataPlaneUrl}/commands with the secret in X-Maritime-Token). Restart the time limit with POST /api/sandboxes/ephemeral/{id} {"timeLimitMinutes":120} (counted from now, default 1440), end it with DELETE /api/sandboxes/ephemeral/{id}, list with GET /api/sandboxes/ephemeral, and read CPU, memory, disk and idleSeconds with GET /v1/sandboxes/{id}/usage. TypeScript: const box = await maritime.ephemeral.createEphemeral({ idleMinutes: 5, timeLimitMinutes: 30 }), const sbx = await maritime.sandboxes.connect(box), await sbx.run("uname -a"), maritime.ephemeral.killEphemeral(box.id). Python: box = client.ephemeral.create_ephemeral(idle_minutes=5, time_limit_minutes=30), client.sandboxes.connect(box["id"]).run("uname -a"), client.ephemeral.kill_ephemeral(box["id"]). CLI (--time-limit sets the time limit, --idle the idle time, default 5 minutes): maritime sandboxes create --time-limit 30m, maritime sandbox exec <sandbox_id> -- uname -a, maritime sandboxes extend <sandbox_id> --time-limit 2h, maritime sandboxes kill <sandbox_id>. MCP: create_ephemeral_sandbox. Code written for E2B also works: set E2B_DOMAIN=sb.mtm.sh and E2B_API_KEY to the Maritime key (E2B_API_URL only to override https://api.sb.mtm.sh, which serves the /e2b routes).

Swarm

One command across many sandboxes, each ended when its job ends. Invite-only for now: until the user requests access on the dashboard's Swarms page, a start returns 403 swarms_access_required.

maritime swarms run --command "python rollout.py" --count 50 --wait
maritime swarms results <swarm_id> -o results.jsonl

REST: POST /api/swarms with command (required), count or inputs (one {args, env, files} per sandbox), size ({"vcpus":1|2|4,"memoryGb":1..8}, default 2 vCPU and 4 GB; each sandbox runs at exactly that, with 12 GB of disk), timeoutSeconds, concurrency, template (default base) or image (an OCI image). Each sandbox in GET /api/swarms/{id} has a sandboxId (sbx-..."). Then GET /api/swarms/{id}, POST /api/swarms/{id}/cancel, GET /api/swarms/{id}/results(JSON Lines). TypeScript:const run = await maritime.swarms.start({ command: "python rollout.py", count: 50 }), then await maritime.swarms.wait(run.id). Python: client.swarms.start("python rollout.py", count=50), client.swarms.wait(id). MCP: start_swarm, list_swarms, get_swarm, get_swarm_results, cancel_swarm`. Per run: up to 100 sandboxes on Starter, 1,000 on Growth, 5,000 on Scale, 10,000 on Dedicated; as many at once as the plan's awake limit.

Computers on demand: Linux and Windows desktops, Android phones

One computer per end user: POST /api/v1/computers returns the existing one for the same externalUserId (200) or creates it (201). Pick the system with "os": "linux" | "windows" | "android".

curl -s -X POST https://api.maritime.sh/api/v1/computers -H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" -d '{"externalUserId":"user_42","os":"linux"}'
curl -s -X POST https://api.maritime.sh/api/v1/computers/COMPUTER_ID/actions -H "Authorization: Bearer $MARITIME_API_KEY" -H "Content-Type: application/json" -d '{"action":"left_click","coordinate":[412,230]}'

Then: GET /api/v1/computers/{id}/screenshot, POST .../actions (screenshot, left_click, double_click, type, key, scroll, left_click_drag and more; batch up to 50 with {"actions":[...]}), POST .../exec (shell, up to 30 s), GET/PUT .../files, POST .../viewer with {"mode":"control"} for a takeover link a person opens, POST .../sleep, DELETE /api/v1/computers/{id}. TypeScript: maritime.computers.create({ externalUserId, os: "windows" }) (os is linux, windows or android; arch: "arm64" for Android only), .act(id, action), .screenshot(id), .exec(id, cmd), .viewer(id, { mode: "control" }). Python: client.computers.create(external_user_id="user_42", os="android"), client.computers.list(), client.computers.delete(id). CLI: maritime computers create --os windows, maritime computers create --os android --arch arm64, computers list, computers delete <id>.

MCP, the fastest path for a model that drives a screen:

claude mcp add --transport http maritime-computers https://mcp.maritime.sh/mcp --header "Authorization: Bearer mk_..."

Tools: get_computer (user_id, optional os), computer and computer_batch (computer_id, action), run_shell, read_file, write_file, request_takeover (computer_id, reason), takeover_status, close_computer (ends the session; the computer stays and sleeps), delete_computer (deletes the computer and its disk for good). Windows exec runs PowerShell and its disk is C:\data. Android app actions and the adb tunnel are described at https://maritime.sh/docs/phones. A sleeping Windows desktop wakes in about a minute. GET /api/computers/plan reports linuxAvailable, windowsAvailable and androidAvailable; a type with no host here answers 403 machine_unavailable on every plan.

Agent

Host the agent itself: it sleeps when idle and wakes on a message, webhook or schedule.

maritime create support-bot -t openclaw          # templates: openclaw, hermes, zeroclaw, claude_code, codex, dsh
maritime create my-bot --repo https://github.com/acme/agent
maritime chat support-bot "hello"
maritime logs support-bot

REST: POST /api/agents with templateId, or githubRepo (Dockerfile at the root), or "framework":"custom","imageName":"ghcr.io/..."; chat POST /api/agents/{id}/chat {"message":"..."}; logs GET /api/agents/{id}/logs. TypeScript: maritime.agents.create({ name, template }), maritime.agents.chat(id, "hello"). Schedules: maritime triggers create <agent> --type cron --cron "0 9 * * *" --prompt "...". The container contract (listen on $PORT, GET /health, POST /chat within 30 s, persist under /data): https://maritime.sh/docs/frameworks/custom.md. Most templates call models through Maritime's model proxy at the provider's list price, with no markup, from your AI credits.

End users

Your users as records keyed by your own id, each with their own machines, policy and usage.

TypeScript: maritime.endUsers.upsert("user_42", { displayName }), maritime.agents.provision({ externalId: "user_42", name: "assistant-42", template: "openclaw" }) (idempotent, safe on every sign-in), maritime.endUsers.setPolicy("user_42", { llmSpendCapCentsPerMonth: 500 }), maritime.endUsers.mintToken("user_42", { scopes: ["chat"] }) (a short-lived eu_ token for their browser), maritime.endUsers.activity("user_42"). REST under /api/v1/end-users/{external_id}; CLI maritime users upsert|create-agent|set-policy|mint-token|list. Cost per end user: GET /api/v1/usage?from=...&to=...&externalUserId=user_42.

Activity and usage

GET /api/activity?limit=50 is the account feed (starts, sleeps, wakes, errors, triggers); GET /api/agents/{id}/activity and GET /api/v1/end-users/{id}/activity narrow it. Spend so far this period: GET /api/billing/metered-usage (credit, used, overage, by machine type).

Add-ons: when to use them

Add-ons have no separate price on the usage plans yet; warm pools are billed as the machine time their spare machines use.

Add-on Use it when How
Static IP A partner or customer must allowlist your traffic PUT /api/v1/network/workloads/{kind}/{id} (kind agent or computer) {"exit":{"mode":"fixed"}}; CLI maritime agents network <id> --egress fixed
Residential IP A site blocks data centre traffic Same route, {"exit":{"mode":"country","countryCode":"DE"}}; CLI --egress country:DE
Private network The agent must reach a customer's office systems POST /api/v1/network/private-networks, then install the connector; CLI maritime connect --allow host:port
Internet policy presets Limit which sites a machine may reach /api/v1/network/presets; CLI maritime network presets list
Warm pool New users must get a machine in about a second PATCH /api/projects/{project_id} {"warmPoolSize": 5}; spares bill as agent boxes
LLM spend limits Cap what an agent or the account spends on models PUT /api/agents/{id}/llm-spend-limit {"monthlyLimitCents": 2500}; account default PUT /api/llm-spend-limit/default
Dedicated hosts Compliance needs servers of your own On request: https://maritime.sh/contact

Working rules