Docs · Reference

API reference

The HTTP API that mat and the dashboard use. Requests and responses are JSON unless a route says otherwise.

API referenceMenu

Basics

Authentication

Send an API key or a session token as a bearer token:

header
Authorization: Bearer mk_…
  • API keys start with mk_. Create and revoke them on the dashboard's API keys page. They do not expire.
  • Session tokens start with ms_. mat login gets one; it lasts 7 days.
  • A few routes need a signed-in person and refuse API keys with 403: managing API keys, billing checkout and portal, and signing out.

Requests

  • Unknown JSON fields are refused with 400, so a typo never passes silently.
  • Times are RFC 3339 in UTC. IDs have a prefix: sb- sandboxes, ex- commands, snap- snapshots, key- API keys, acct- accounts.
  • Everything you name is scoped to your account. Another account's sandbox is not found, as if it did not exist.
  • Send an Idempotency-Key header with POST /v1/sandboxes to retry a create safely.

Errors

Every error has the same body. The message is written for people and safe to show them.

json
{  "error": {    "code": "quota_exceeded",    "message": "this account may run at most 10 sandboxes at once; pause or delete one"  }}

See Limits and errors for every code.

Streaming output

GET /v1/sandboxes/{id}/execs/{exec_id}/events streams newline-delimited JSON. Output arrives as base64 in data, because it is arbitrary bytes. offset counts all output bytes before the chunk, across both streams. To resume after a dropped connection, reconnect with ?after= set to the offset of the last chunk plus its decoded length. A sandbox keeps the last 1 MiB of each command's output; if you ask for older bytes, a truncated event says where the kept output begins. The stream ends with exited, whose code is the exit code, or null with signal set when a signal ended it.

Connecting a shell or browser

  1. Call POST /v1/sandboxes/{id}/connect with {"kind": "shell"} or {"kind": "browser"}, authenticated as usual.
  2. Open the wss:// URL it returns within 60 seconds. The token in it works once. For a shell, append ?cols=N&rows=N.
  3. Shell: send keystrokes and receive output as binary messages; send resizes and receive the exit as JSON text messages. Browser: speak the Chrome DevTools Protocol.

Sandboxes

POST/v1/sandboxes

Create a sandbox. Returns once it is running. An empty body means all defaults.

Auth
API key or session
Success
201 Created

Headers

Idempotency-Key
1 to 128 visible ASCII characters. Retrying with the same key returns the original sandbox instead of creating another.
request body
{  "size": "small",  "keep_awake": false,  "idle_timeout_minutes": 10,  "egress": { "mode": "restricted", "allow": ["example.com", "*.example.org"], "essentials": true },  "secrets": ["ANTHROPIC_API_KEY"]}
response
{  "id": "sb-4f1c9e27a0b3d5c86e12",  "size": "medium",  "status": "running",  "keep_awake": false,  "idle_timeout_minutes": 10,  "egress": { "mode": "open" },  "egress_pending": false,  "secrets": [],  "created_at": "2026-10-30T09:12:44.512731Z",  "cold_booted_at": null,  "forked_from": null}

Errors

  • 400 invalid_requestidle_timeout_minutes must be 1-1440
  • 400 invalid_requestno such secrets: NAME
  • 400 invalid_request"…" is not a domain or *.domain
  • 400 invalid_requestIdempotency-Key must be 1-128 visible ASCII characters
  • 429 quota_exceededtoo many sandboxes created this minute; retry in N seconds
  • 429 quota_exceededthis account has no sandbox quota yet; ask us to enable it
  • 429 quota_exceededthis account may have at most N sandboxes
  • 429 quota_exceededthis account may use at most N MiB of sandbox memory
  • 429 quota_exceededthis account may run at most N sandboxes at once; pause or delete one
  • 503 unavailableno capacity available right now; retry shortly
  • All fields are optional. size is small, medium (default) or large. egress.mode is open (default), restricted or none; essentials defaults to true. Up to 256 allowed domains and 64 secrets.
  • When no host has room, the create may wait up to a minute for room before answering 503.
  • Every attempt counts toward the 60-per-minute create limit, including retries with an Idempotency-Key.

GET/v1/sandboxes

List your sandboxes.

Auth
API key or session
Success
200 OK
response
{ "sandboxes": [ { "id": "sb-4f1c9e27a0b3d5c86e12", "status": "running", … } ] }
  • Never wakes a sandbox.

GET/v1/sandboxes/{id}

Show one sandbox.

Auth
API key or session
Success
200 OK
response
{  "id": "sb-4f1c9e27a0b3d5c86e12",  "size": "medium",  "status": "running",  "keep_awake": false,  "idle_timeout_minutes": 10,  "egress": { "mode": "open" },  "egress_pending": false,  "secrets": [],  "created_at": "2026-10-30T09:12:44.512731Z",  "cold_booted_at": null,  "forked_from": null}

Errors

  • 404 not_foundno sandbox sb-…
  • Never wakes a sandbox.
  • status is creating, running, pausing, paused, resuming, stopped, deleting, deleted or failed. See Sandbox states in Concepts.
  • cold_booted_at is when it last booted fresh from its files; processes from before then are gone. forked_from is the snapshot a fork was made from.
  • held appears only on a sandbox held for review after suspected abuse, with the reason. Until it is cleared, anything that would run it (exec, files, shell, resume, restore, fork) fails with 403 forbidden.

PATCH/v1/sandboxes/{id}

Change settings. Fields you leave out stay as they are.

Auth
API key or session
Success
200 OK
request body
{  "keep_awake": true,  "idle_timeout_minutes": 30,  "egress": { "mode": "none" },  "secrets": ["ANTHROPIC_API_KEY"]}

ResponseThe updated sandbox.

Errors

  • 404 not_foundno sandbox sb-…
  • 400 invalid_requestidle_timeout_minutes must be 1-1440
  • secrets replaces the attached set; [] detaches all.
  • An egress change applies to new connections. egress_pending stays true until the host confirms it enforces the new policy.
  • Never wakes a sandbox.

DELETE/v1/sandboxes/{id}

Delete a sandbox with its files and memory.

Auth
API key or session
Success
200 OK

ResponseThe sandbox, with "status": "deleted", or "deleting" if the host has not confirmed yet (we keep trying).

Errors

  • 404 not_foundno sandbox sb-…
  • 409 conflictthe sandbox is creating; delete it once that finishes
  • Its snapshots are deleted with it. This cannot be undone.
  • A pause, resume or create in progress gets time to finish first. Deleting a deleted sandbox succeeds.

POST/v1/sandboxes/{id}/pause

Pause now: memory and files are saved, nothing runs.

Auth
API key or session
Success
200 OK

ResponseThe sandbox, with "status": "paused".

Errors

  • 404 not_foundno sandbox sb-…
  • 409 conflictcannot pause a sandbox that is stopped

POST/v1/sandboxes/{id}/resume

Resume a paused or stopped sandbox. Any use resumes it too.

Auth
API key or session
Success
200 OK

ResponseThe sandbox, with "status": "running".

Errors

  • 404 not_foundno sandbox sb-…
  • 429 quota_exceededthis account may run at most N sandboxes at once; pause or delete one
  • 503 unavailablethe sandbox's host is busy; retry shortly
  • 503 unavailablethe sandbox is still pausing or resuming; retry shortly
  • A stopped sandbox boots fresh from its files: they are kept, processes are not, and cold_booted_at is set.

Commands

A command runs as the user agent with a clean environment and no standard input. It keeps running when you disconnect, and its output is kept in the sandbox so you can reattach.

POST/v1/sandboxes/{id}/execs

Start a command. Returns once it is running.

Auth
API key or session
Success
201 Created
request body
{  "argv": ["sh", "-c", "npm test && npm run build"],  "cwd": "/workspace/app",  "env": { "CI": "1" }}
response
{ "id": "ex-7c2d90a1e4b35f8d6a01", "sandbox_id": "sb-4f1c9e27a0b3d5c86e12", "pid": 412 }

Errors

  • 404 not_foundno sandbox sb-…
  • 400 invalid_requestargv must not be empty
  • 429 quota_exceededthis account may run at most N sandboxes at once; pause or delete one
  • argv is passed to the program as-is. For shell syntax, ask for a shell: ["sh", "-c", "…"].
  • cwd defaults to /workspace. Your env is applied on top of the sandbox's secret placeholders.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.

GET/v1/sandboxes/{id}/execs/{exec_id}/events

Stream a command's output from a byte offset, then its exit, as newline-delimited JSON.

Auth
API key or session
Success
200 OK, Content-Type: application/x-ndjson

Query

after
Byte offset to start from (default 0). Pass the offset of the last byte you received to resume.
response
{"type":"output","stream":"stdout","offset":0,"data":"aGVsbG8K"}{"type":"output","stream":"stderr","offset":6,"data":"d2FybmluZwo="}{"type":"truncated","retained_from":1048576}{"type":"exited","code":0,"signal":null,"output_bytes":14}

Errors

  • 404 not_foundno such exec
  • 400 invalid_requestafter must be a byte offset
  • See Streaming output below for how offsets work.
  • Open streams count as activity, so the sandbox stays awake while you watch.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.

POST/v1/sandboxes/{id}/execs/{exec_id}/signal

Signal the command and its children.

Auth
API key or session
Success
204 No Content
request body
{ "signal": "interrupt" }
  • signal is interrupt (SIGINT), terminate (SIGTERM) or kill (SIGKILL).

Files

One file at a time, streamed both ways, up to 1 GiB. The path is an absolute path to a file inside the sandbox.

GET/v1/sandboxes/{id}/export

A gzipped tar archive of a file or directory in the sandbox, as a download.

Auth
API key or session
Success
200 OK

Query

path
An absolute path in the sandbox, a file or a directory. Default /workspace.

ResponseThe archive (Content-Type application/gzip, Content-Disposition attachment; filename=SANDBOX.tar.gz).

Errors

  • 404 not_foundno sandbox sb-…
  • 403 forbiddenthis account can run no sandboxes; exports stay open for 30 days after a subscription ends, and that window has closed
  • 409 conflictan export of this sandbox is already running
  • Resumes a paused sandbox. For 30 days after a subscription ends it still works: the sandbox is resumed only for the export and paused again after.
  • If the export fails partway the response ends with an error, so a cut-off archive is never taken for a whole one.

PUT/v1/sandboxes/{id}/files

Upload a file. The body is the raw file contents.

Auth
API key or session
Success
200 OK

Query

path
Absolute path of the file in the sandbox.required
response
{ "size": 1832 }

Errors

  • 400 invalid_requestpath must be an absolute path to a file
  • 400 invalid_requestfile exceeds the 1 GiB transfer limit
  • 503 unavailableThe sandbox's disk is full; the message says so.
  • Missing parent directories are created. The target is replaced only once the upload completes. New files belong to agent.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.

GET/v1/sandboxes/{id}/files

Download a file as application/octet-stream, with its Content-Length.

Auth
API key or session
Success
200 OK

Query

path
Absolute path of the file in the sandbox.required

ResponseThe file's bytes.

Errors

  • 404 not_foundThe file does not exist.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.

Shell and browser connections

WebSocket connections use a two-step flow, so your API key or session never appears in a URL. See Connecting a shell or browser below.

POST/v1/sandboxes/{id}/connect

Mint a single-use WebSocket URL for a shell or the browser.

Auth
API key or session
Success
200 OK
request body
{ "kind": "shell" }
response
{ "url": "wss://…/v1/connect/ct_…", "expires_at": "2026-10-30T09:31:05.118204Z" }

Errors

  • 404 not_foundno sandbox sb-…
  • 400 invalid_requestinvalid JSON: …
  • 409 conflictthe sandbox is deleted
  • kind is shell or browser. The URL works once, within 60 seconds, and only for this sandbox and kind.

GET/v1/connect/{token}

Open the WebSocket. The token in the path is the credential.

Auth
The single-use token in the path
Success
101 Switching Protocols

Query

cols
Shell only: terminal width, 1 to 1000 (default 80).
rows
Shell only: terminal height, 1 to 1000 (default 24).

Errors

  • 401 unauthorizedmissing, invalid or expired credentials; sign in again
  • 503 unavailabletoo many open connections; retry shortly
  • 401 means the token is unknown, expired or already used: mint a new one.
  • Shell: keystrokes and output are binary messages. Text messages carry JSON: you send {"type":"resize","cols":120,"rows":40}; you receive {"type":"exited","code":0,"signal":null} or {"type":"error","message":"…"}.
  • Browser: the WebSocket speaks the Chrome DevTools Protocol. Pass the URL to a CDP client, such as Playwright's connectOverCDP.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.
  • An open connection keeps the sandbox counted as active.

Network and credentials

GET/v1/sandboxes/{id}/activity

What keeps the sandbox from pausing on its own now.

Auth
API key or session
Success
200 OK
response
{  "status": "running",  "idle_seconds": 0,  "idle_timeout_minutes": 10,  "keep_awake": false,  "reasons": ["waiting for an answer from api.anthropic.com"]}

Errors

  • 404 not_foundno sandbox sb-…
  • Never wakes a sandbox. Only a running sandbox has reasons or idle_seconds; reasons is empty when nothing keeps it awake.

GET/v1/sandboxes/{id}/egress

The sandbox's recent outbound connections, oldest first.

Auth
API key or session
Success
200 OK
response
{  "events": [    {      "at": "2026-10-30T09:15:02.004113Z",      "host": "example.net",      "destination": "93.184.215.14:443",      "outcome": "denied",      "reason": "not on the allowlist",      "secrets": [],      "count": 1    }  ]}
  • outcome is allowed, denied or failed (allowed, but unreachable). host is the name the connection asked for, or null for protocols that carry none. count is how many similar connections the entry stands for; at is the latest.
  • Never wakes a sandbox.

GET/v1/sandboxes/{id}/credentials

The credential inventory: which credentials the sandbox holds and where, never their values.

Auth
API key or session
Success
200 OK
response
{  "credentials": [    { "kind": "aws_profile", "name": "default", "domain": null, "location": "/home/agent/.aws/credentials" }  ]}
  • kind is one of environment, dot_env, aws_profile, github_cli, git_credentials, netrc, npm, pypi, docker, ssh_key, kubeconfig, gcloud, agent_login, cookies.
  • Resumes the sandbox if it is paused or stopped. A resume counts toward max_running, so this can answer 429.

GET/v1/sandboxes/{id}/preview

Open a preview of a web server in the sandbox: a redirect into the preview.

Auth
The dashboard's session cookie only
Success
302 Found

Query

port
The port the server listens on, 1 to 65535.required

Errors

  • 403 forbiddenpreviews open only from the signed-in dashboard
  • 404 not_foundpreviews are not set up
  • 400 invalid_requestport must be 1-65535
  • Only the signed-in dashboard can open a preview; API keys and mat sessions get 403.
  • The redirect carries a single-use token valid for 60 seconds. The preview then keeps its own cookie, which ends with your dashboard session.

Secrets

Brokered secrets belong to the account. Values can be written and replaced, never read back.

PUT/v1/secrets/{name}

Create or replace a secret.

Auth
API key or session
Success
200 OK
request body
{ "value": "sk-ant-…", "domains": ["api.anthropic.com"] }
response
{ "name": "ANTHROPIC_API_KEY", "domains": ["api.anthropic.com"], "sandboxes": 2, "updated_at": "2026-10-30T09:20:11.730015Z" }

Errors

  • 400 invalid_request"…" is not a valid secret name: use letters, digits and _
  • 400 invalid_requestvalue must be 1-8192 bytes of printable ASCII
  • 400 invalid_requestbind 1-16 domains
  • 400 invalid_request"…" is not a domain or *.domain
  • name is letters, digits and _, not starting with a digit, up to 128 characters. It is also the environment variable the placeholder appears as.
  • Replacing a value updates every sandbox it is attached to. sandboxes counts them.

GET/v1/secrets

List secrets: names, domains and use, never values.

Auth
API key or session
Success
200 OK
response
{ "secrets": [ { "name": "ANTHROPIC_API_KEY", "domains": ["api.anthropic.com"], "sandboxes": 2, "updated_at": "…" } ] }

DELETE/v1/secrets/{name}

Delete a secret and detach it from every sandbox.

Auth
API key or session
Success
204 No Content

Errors

  • 404 not_foundno secret NAME

Account and API keys

GET/v1/me

Who is calling, and the account's limits and current use.

Auth
API key or session
Success
200 OK
response
{  "account_id": "acct-5d2e8a41c09b7f36",  "user": { "name": "Ada Lovelace", "email": "ada@example.com", "provider": "github" },  "limits": {    "max_sandboxes": 50, "max_memory_mib": 204800, "max_running": 10,    "sandboxes": 3, "memory_mib": 10240, "running": 1  }}
  • user is null when calling with an API key. sandboxes and memory_mib count every sandbox not deleted, paused ones included; running counts those creating, running, resuming or pausing.

GET/v1/keys

List the account's API keys, never their secrets.

Auth
Signed-in session only; API keys get 403
Success
200 OK
response
{ "keys": [ { "id": "key-1a2b3c4d5e6f7a8b", "label": "ci", "created_at": "…" } ] }

POST/v1/keys

Create an API key. Its secret is shown only in this response.

Auth
Signed-in session only; API keys get 403
Success
201 Created
request body
{ "label": "ci" }
response
{ "id": "key-1a2b3c4d5e6f7a8b", "label": "ci", "key": "mk_…", "created_at": "…" }

Errors

  • 400 invalid_requestlabels are up to 64 printable characters
  • 403 forbiddensign in to do this; API keys cannot

DELETE/v1/keys/{id}

Revoke an API key at once.

Auth
Signed-in session only; API keys get 403
Success
204 No Content

Errors

  • 404 not_foundno API key key-…

Billing

GET/v1/billing

Billing status and this calendar month's usage (UTC), in hours per size.

Auth
API key or session
Success
200 OK
response
{  "status": "active",  "plan": "pay_as_you_go",  "month": "2026-10",  "usage": [    { "size": "small", "running_hours": 0.0, "paused_hours": 0.0 },    { "size": "medium", "running_hours": 41.25, "paused_hours": 310.5 },    { "size": "large", "running_hours": 0.0, "paused_hours": 0.0 }  ],  "can_manage": true}
  • status is off (billing is not set up on this deployment), none (no payment method), pending (checkout finished, subscription not active yet), active, past_due (a payment failed and Stripe is retrying) or ended (cancelled or unpaid).
  • plan is pay_as_you_go while subscribed, otherwise null. paused_hours covers paused and stopped time, billed as storage. can_manage says whether the billing portal can open.

POST/v1/billing/checkout

A Stripe Checkout page to add a payment method and subscribe.

Auth
Signed-in session only; API keys get 403
Success
200 OK
response
{ "url": "https://checkout.stripe.com/…" }

Errors

  • 403 forbiddensign in to do this; API keys cannot
  • 404 not_foundbilling is not set up here
  • 409 conflictthis account already has a subscription; use Manage billing

POST/v1/billing/portal

Stripe's billing portal, for cards and invoices.

Auth
Signed-in session only; API keys get 403
Success
200 OK
response
{ "url": "https://billing.stripe.com/…" }

Errors

  • 403 forbiddensign in to do this; API keys cannot
  • 404 not_foundbilling is not set up here
  • 409 conflictadd a payment method first

Signing in from a CLI

The device flow mat login uses. You only need it to build your own client for people; programs should use API keys.

POST/v1/auth/cli

Start a sign-in. Show the person user_code and verification_url.

Auth
None
Success
200 OK
response
{  "device_code": "md_…",  "user_code": "KQTR-WMZD",  "verification_url": "https://…/cli?code=KQTR-WMZD",  "expires_at": "2026-10-30T09:22:00Z",  "interval_seconds": 3}
  • Keep device_code to yourself; it collects the session. The code expires after 10 minutes.

POST/v1/auth/cli/token

Poll every interval_seconds until the person confirms the code in the dashboard.

Auth
None
Success
200 OK
request body
{ "device_code": "md_…" }
response
{ "status": "pending" }{ "status": "approved", "token": "ms_…", "expires_at": "2026-11-06T09:12:00Z" }{ "status": "expired" }
  • The token is returned once and lasts 7 days. Expired means expired, already used or unknown: start again.

POST/v1/auth/logout

End the calling session.

Auth
Signed-in session only; API keys get 403
Success
204 No Content

Errors

  • 403 forbiddenAPI keys are revoked, not signed out

Snapshots

New and still being finished; details may change. See Snapshots and fork for what each operation keeps.

POST/v1/sandboxes/{id}/snapshotsnew

Take a snapshot of the sandbox's memory and files. Returns once it is ready.

Auth
API key or session
Success
201 Created
request body
{ "name": "before-migration" }
response
{  "id": "snap-0b9e4a7c21d85f3e6a14",  "sandbox_id": "sb-4f1c9e27a0b3d5c86e12",  "name": "before-migration",  "status": "ready",  "allocated_bytes": 431980544,  "freeze_ms": 240,  "created_at": "2026-10-30T09:40:12.031877Z"}

Errors

  • 404 not_foundno sandbox sb-…
  • 400 invalid_requesta snapshot name is 1-64 characters, without control characters
  • 409 conflictthe sandbox is stopped and has no memory to save; use it to boot it first
  • 429 quota_exceededa sandbox keeps at most 10 snapshots; delete one first
  • 503 unavailablethe sandbox's host has no disk for a snapshot right now; retry later
  • The body is optional; an empty body means no name.
  • allocated_bytes is the disk the snapshot holds on its host, counting parts it still shares with the sandbox. freeze_ms is how long a running sandbox was frozen, 0 if it was paused.

GET/v1/sandboxes/{id}/snapshotsnew

List the sandbox's snapshots.

Auth
API key or session
Success
200 OK
response
{ "snapshots": [ { "id": "snap-0b9e4a7c21d85f3e6a14", "status": "ready", … } ] }

Errors

  • 404 not_foundno sandbox sb-…
  • status is creating, ready, failed (it could not be taken, or its files were lost) or deleted.

DELETE/v1/sandboxes/{id}/snapshots/{snapshot_id}new

Delete one snapshot.

Auth
API key or session
Success
200 OK

ResponseThe snapshot, with "status": "deleted".

Errors

  • 404 not_foundno snapshot snap-…
  • 409 conflictthe snapshot is still being taken; delete it once it is ready

POST/v1/sandboxes/{id}/restorenew

Roll the sandbox back to one of its snapshots, memory and files. It ends paused at that moment.

Auth
API key or session
Success
200 OK
request body
{ "snapshot_id": "snap-0b9e4a7c21d85f3e6a14" }

ResponseThe sandbox, with "status": "paused".

Errors

  • 404 not_foundno snapshot snap-…
  • 409 conflictthe snapshot is creating, not ready
  • 409 conflictthe sandbox is resuming; restore it once that finishes
  • 409 conflictthe snapshot's files are gone
  • The next exec, file or connect call resumes the sandbox with the processes it had at the snapshot.

POST/v1/snapshots/{snapshot_id}/forknew

Create a new sandbox from the snapshot's files. It boots fresh: files are kept, processes are not.

Auth
API key or session
Success
201 Created

Headers

Idempotency-Key
1 to 128 visible ASCII characters. Retrying with the same key returns the fork the first request made.

ResponseThe new sandbox. Its "forked_from" is the snapshot ID.

Errors

  • 404 not_foundno snapshot snap-…
  • 409 conflictthe snapshot is creating, not ready
  • 409 conflictthis Idempotency-Key was already used for another request
  • 403 forbiddenthis sandbox is held for review after suspected abuse (port scanning) and cannot run until it is cleared; contact support
  • 409 conflictthe sandbox the snapshot came from is deleting
  • 429 quota_exceededtoo many sandboxes created this minute; retry in N seconds
  • 503 unavailablethe snapshot's host takes no new sandboxes now; retry later
  • 503 unavailableno capacity available right now on the snapshot's host; retry shortly
  • No body. The fork gets the source's size and its idle, egress and secret settings.
  • It counts as a create: the create rate limit and your quotas apply, with the same errors as POST /v1/sandboxes.
  • It is placed on the snapshot's host, because that is where the files are.