# Maritime: setup procedure for AI agents

> You are an AI agent reading https://maritime.sh/agents.md. This is a procedure.
> Execute the steps in order. It is idempotent: a step that is already done
> is skipped, not repeated. Do not merely explain the steps to the user.
> To choose between sandboxes, swarms, desktops, phones and agents,
> and for every product's calls and prices, read https://maritime.sh/agent-guide.md.

Maritime is a cloud platform that gives every AI agent its own computer:
Persistent sandboxes, Ephemeral sandboxes (with Swarms) and Computers on
demand (Linux and Windows desktops and Android phones). An agent you deploy here, from a
template, a GitHub repo or a Docker image, runs in a Persistent sandbox: a
real Firecracker micro-VM with a disk at /data that survives sleep, restart,
and redeploy. It sleeps when idle, paying only for its disk, and wakes in
about a second.

Billing, so you can tell the user what this costs: Plans are 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 Growth, Scale and Dedicated also take 10%,
20% and 30% off every rate; on Dedicated the Maritime team also sets up
your apps, images and network with you. Machines are billed per second at
hourly rates while awake: a persistent sandbox is $0.03 per vCPU-hour plus
$0.01 per GB-hour (1 vCPU and 2 GB is $0.05 an hour), an ephemeral sandbox
or one sandbox of a swarm is $0.0045 per vCPU-hour plus $0.00225 per
GB-hour (the default small size, 2 vCPU and 4 GB, is $0.018 an hour), a
Linux desktop $0.12 an hour, a Windows desktop $0.18 and an Android phone
$0.15. A sleeping machine pays only for its disk,
$0.08 per GB-month. Usage past the credit is billed at the same rates on
the same invoice. New accounts start with a 7-day free trial: a card is
needed to start the first machine 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. Details: https://maritime.sh/pricing

Ask the user a question only when their input is genuinely required: their
email and password for a new account, which agent to deploy when there are
several, and approving a device login or clicking a verification link in
their inbox.

## Step 1: identify yourself and your environment

Note which of these you are, it changes step 3:

- Claude Code, Codex CLI, Cursor, Gemini CLI, OpenCode, Aider: you run in the
  user's terminal, usually with no browser you can open. Prefer the device
  login.
- An agent inside a sandbox with no outbound browser at all: same as above.
- A desktop app with a browser: the browser login also works.

Check the environment:

```bash
node --version                 # need 18 or later
maritime --version             # absent means install below
```

## Step 2: install the CLI

```bash
curl -fsSL https://maritime.sh/install.sh | sh    # no Node needed; checks the download's sha512
# or: npm install -g maritime-cli        # the command is `maritime`
maritime --version
```

If a flag in this procedure is unknown to the installed CLI, run
`npm install -g maritime-cli@latest` and retry once.

## Step 3: onboard (sign in, API key, payment) in one command

```bash
maritime onboard --json
```

It runs only the steps the account still needs, and is safe to run again.
It prints one JSON object. Branch on `step`:

- `sign_in`: give the person `sign_in.url` and say the page shows the
  code `sign_in.code` for them to confirm. They sign in with Google,
  GitHub or email. When the account has no card yet, the same page continues
  straight to Stripe Checkout, so the person follows one link. Then run
  `next_command` (`maritime onboard --wait --json`).
- `checkout`: the account needs a payment method. Two ways, pick one:
  1. A person adds a card: give them `checkout_url` (Stripe Checkout). The
     first 7 days are free, nothing is charged today.
  2. You pay yourself, when `agent_pay` is present and the person has a
     Stripe Link wallet: run `agent_pay.pay_command`
     (`npx @stripe/link-cli mpp pay <url> -X POST`). The server answers
     HTTP 402 with a Stripe Challenge, link-cli asks the person to approve
     one payment in the Link app, and pays with a Shared Payment Token. The
     plan starts at once (no free week). No card form.
  Then run `next_command`.
- `billing_action`: a failed payment or a used-up free week. Give the
  person `billing_url`.
- `done`: ready. `api_key` (shown once) is a long-lived `mk_` key, already
  saved for the CLI; export it as MARITIME_TOKEN for SDK or HTTP use. Go to
  step 5.

Running each step yourself instead: `maritime auth login`,
`maritime keys create --name <name> --json`, `maritime billing checkout --json`
or `maritime billing pay-link --json`, `maritime billing status --json`.
Never quote a price from memory: `maritime billing status --json` and
https://maritime.sh/pricing have the live numbers.

### Other ways to authenticate

When onboard does not fit, try these in order and stop at the first that works.

1. Already signed in: `maritime whoami --json` exits 0. It exits 4 when no
   credential is set or a login token expired. It does not call the API, so
   confirm with `maritime doctor`. Continue to step 4.
2. `MARITIME_TOKEN` is set in the environment: the CLI reads it first. Run
   `maritime doctor` to check it against the API.
3. The user has an account, with or without a browser:
   `maritime login --json`. The CLI prints one JSON line on stderr with
   `user_code` and `verification_uri_complete`. Show both to the user,
   ask them to open the URL and approve, and wait. The command returns when
   they approve.
4. The user has an email/password account and wants no browser step:
   `maritime login --email <email> --password <password>`.
5. The user has no account and is present: ask for an email and a password,
   then `maritime signup -e <email> -p <password> -n <name> --json`. Never
   invent an email. Signup saves a login token and returns
   `emailVerified: false`.
6. The user has no account and you should not stop to ask: deploy first and
   let them claim it later. Skip to step 6 and add `--claim-later` to the
   create command. It needs no login: the agent runs under a temporary
   account for 72 hours. The JSON response carries `data.claimUrl` and
   `data.token`, a temporary key for that agent; export it as
   MARITIME_TOKEN to operate the agent. One agent is the limit. A template agent must bring
   its own model key (`-e ANTHROPIC_API_KEY=...` or `-e OPENAI_API_KEY=...`)
   because the temporary account has no AI credits. Put the claim link in
   your final report; the user opens it, signs in or signs up, and the agent
   moves into their account.

Do not print the password back. Do not paste tokens into chat.

## Step 4: the email verification wall

Until the user clicks the link Maritime emailed them, these are refused with
HTTP 403 and `email_not_verified` in the error (exit 4): create, deploy,
start, restart, keys create, computer creation. Listing, status, logs, and templates
work before verification.

When you see `email_not_verified`: tell the user to open their inbox and
click the link; if it did not arrive, they sign in at https://maritime.sh/login,
which offers to resend it. Wait
for them to say it is done, then retry the same command. Do not create a
second account.

## Step 5: learn the contract

```bash
maritime guide --json          # every command and flag, from the installed CLI
```

Rules that apply to every command: pass `--json`; results are JSON on
stdout; errors print `Error: ...` on stderr with the HTTP status and the
API's response body; exit codes are 0 ok, 1 other error, 2 usage, 3 not
found, 4 auth, 5 API error, 6 partial failure, 7 rate limited, 10 config.
Never guess flags. Full contract: https://maritime.sh/docs/cli/llms.txt

## Step 6: deploy

Choose exactly one recipe.

A. The user wants a ready-made agent (no code of their own):

```bash
maritime templates --json                                   # ids: openclaw, hermes, zeroclaw, claude_code, codex, dsh
maritime create <name> --template openclaw --json
```

B. The user has a repo with a Dockerfile at its root (any framework):

```bash
maritime create <name> --repo <git url> --json              # builds on Maritime, 1 to 3 minutes
```

The container must listen on 0.0.0.0:$PORT (injected, never hardcode 8080),
answer GET /health with any 2xx, answer POST /chat with JSON
{"message": "..."} within 30 seconds, and keep state under /data. If the repo
does not meet this, add the smallest server that does before deploying.
Contract and examples: https://maritime.sh/docs/frameworks/custom.md

C. The user wants a public web app on its own HTTPS URL:

```bash
maritime create <name> --repo <git url> --public --port <port> --json
```

D. The user's product should give every end user their own agent: stop, and
point them at the SDK instead of deploying by hand: https://maritime.sh/docs/build.md

Secrets the agent needs go in after create, never into the repo:

```bash
maritime env set <name> KEY=value --reload --json
```

Scheduled work: timers inside a sleeping VM do not fire. Use
`maritime triggers create <name> --type cron --cron "<expr>" --json`.

## Step 7: verify

```bash
maritime status <name> --json                               # poll until .status is "active"
maritime chat <name> "hello" --json | jq -r '.response'     # templates and chat-capable containers
maritime logs <name> --json                                 # custom containers: the server is listening
```

A build failure shows in `maritime history <name> --json` (buildLog). Fix
the Dockerfile and run `maritime deploy <name> --json`. Do not create a
second agent to work around a failed build.

## Step 8: leave the repo ready for next time

If you deployed from a repo and `.claude/skills/maritime/SKILL.md` does not
exist, run `maritime init --no-create` there (it writes the skill and a
maritime.json), or fetch https://maritime.sh/SKILL.md into that path. The next agent
session in that repo then knows Maritime without reading this page.

## Step 9: report

End with exactly this shape, filled in with what actually happened:

- Status: deployed and verified | deployed, not verified | blocked (why)
- Agent: <name>, <template or repo>, dashboard https://maritime.sh/agents
- Claim link, if you used --claim-later: the URL and the expiry time
- Auth: which path from step 3 was used; whether verification is pending
- Changes made: files added or edited in the repo, env vars set (names only)
- Verification: the status value and the chat reply or log line you saw
- Next action for the user: one line, or "none"

Do not finish with a list of steps that were not attempted. Execute the
procedure first, then report the actual result.

## Known behaviours

- `maritime login` (no flags) prints a code and a URL and waits for the
  user to approve it in any browser. It does not need a browser on this
  machine.
- Without a terminal, `create` with no name and `delete` without
  `--yes` exit 2 instead of prompting.
- Flag-parse errors (unknown option, missing argument) exit 2 with plain text
  on stderr. Check `maritime <command> --help`.
- `agent_unavailable` in a chat error: the agent is not running. Run
  `maritime start <name>`, wait a few seconds, retry.
- A create refused for billing exits 5 with HTTP 402 in the error (exit 7 for
  HTTP 429 when too many machines are awake); the API body on stderr carries
  a code such as `trial_required` (add a card to start the free trial),
  `trial_cap_reached`, `plan_required`, `payment_required`,
  `concurrency` or `seat_limit`. Show the API's message to the user
  unchanged; it names the fix.
- Most templates need no model key: they call models through Maritime's
  model proxy, charged at the provider's list price from the account's model
  budget.
  `claude_code` needs ANTHROPIC_API_KEY as a secret env var.
- The `--claim-later` temporary key cannot create a second agent
  (`seat_limit`) and expires with the claim link. After the claim, the user mints a normal key.
- Long-running automation should use an API key, not a login token:
  `maritime keys create --name <purpose> --json` (after verification) and
  export `data.raw_key` as MARITIME_TOKEN. mk_ keys never expire unless
  created with `--expires-in-days`.

## More

- Index of every page: https://maritime.sh/llms.txt
- Every docs page as markdown: append .md to its URL, for example
  https://maritime.sh/docs/quickstart.md
- REST API: https://api.maritime.sh/openapi.json
- The skill file for your skills directory: https://maritime.sh/SKILL.md
