Skip to main content
The Mirage server serves JSON over HTTP, every route under /v1, the same in Python and TypeScript. The CLI is a client of these routes.

Run it

The server is a FastAPI app in Python and a Fastify app in TypeScript. Run it as your own service:
It answers only the Host names in allowed_hosts (MIRAGE_ALLOWED_HOSTS), 127.0.0.1, localhost and ::1 by default, so add the name your clients use. On your own machine the CLI starts the same server as a daemon, which exits 30 seconds after its last workspace is deleted. Options are on the Python and TypeScript server pages.

Auth

Every route but GET /v1/health and GET /.well-known/oauth-protected-resource needs Authorization: Bearer <token>. MIRAGE_AUTH_MODE decides what the server accepts: In jwt mode the server checks the signature against that public key, or against the key set your issuer publishes at MIRAGE_JWT_JWKS_URL; it keeps that set and fetches it again for a key id it does not hold, so a key rotation needs no restart (a fixed key needs one). It accepts only the pinned algorithm, requires exp and sub, and checks iss when MIRAGE_JWT_ISSUER is set. A token with aud must name one of MIRAGE_JWT_AUDIENCE, a comma-separated list. A token without aud must carry an azp in MIRAGE_JWT_AUTHORIZED_PARTIES when that is set, and is refused when only an audience is. So one server takes both your app’s session tokens and an OAuth client’s access tokens, as Clerk issues them:

CLI login

With MIRAGE_LOGIN_CLIENT_ID set, GET /.well-known/oauth-protected-resource tells mirage login where to sign in: the issuer (MIRAGE_JWT_ISSUER) and the OAuth client. The CLI reads the issuer’s endpoints from its /.well-known/oauth-authorization-server, opens the browser on its sign-in, and swaps the code it gets back for tokens with PKCE, so no secret is kept on the user’s machine. The setting needs jwt mode and an issuer, and MIRAGE_JWT_AUDIENCE must list the client. Without it the route answers 404, and mirage login reports that the server needs no login. In Clerk, create an OAuth application with no client secret and PKCE on, add the redirect http://127.0.0.1/callback (the CLI picks a free port), allow the offline_access scope so the CLI gets a refresh token, and turn the consent screen off so a signed-in user goes straight back to the CLI. Its access tokens last a day and the CLI refreshes them; it asks the user to log in again after 30 days.

Accounts

In jwt mode the token’s sub is the caller’s account, and an account reaches only the workspaces it created:
  • Another account’s workspace answers 404 on every route, the same as a missing one, and so do its sessions, jobs and asks. GET /v1/workspaces and GET /v1/jobs list only the caller’s.
  • The owner is kept under the state root, so after a restart a stored workspace reopens only for its owner. A workspace stored before the server ran in jwt mode has no owner, and no account can create its id.
  • Snapshot keys live under accounts/<account>/ in the snapshot store.
  • POST /v1/shutdown answers 403.
An SSH key belongs to an account through its mirage-account option.
Accounts keep workspaces apart, not the host: a workspace config can still mount a host directory or run the local runtime. In local and token mode any accepted token can use every route and every workspace, including POST /v1/shutdown. In local mode with no token in the environment or in ~/.mirage/auth_token, the server asks for none.

Errors

A failed request answers {"detail": "<message>"}.

Server

Check health

GET /v1/health Answers {"status": "ok", "workspaces": 2, "uptime_s": 12.5}, without a token.

Shut down

POST /v1/shutdown Asks the server to stop. The daemon the CLI starts closes its workspaces and exits; a server you run yourself stops only if you gave it on_idle_exit (onIdleExit in TypeScript).

Workspaces

Create a workspace

POST /v1/workspaces
object
required
The workspace YAML as JSON.
string
The workspace id. A fresh one when absent.
Answers 201 and the workspace. The same id and config again answers 200 and that workspace; another config under a taken id answers 409.

List workspaces

GET /v1/workspaces

Get a workspace

GET /v1/workspaces/{id}
boolean
default:"false"
Add cache and job internals.

Delete a workspace

DELETE /v1/workspaces/{id} Closes the workspace and deletes its state.

Close a workspace

POST /v1/workspaces/{id}/close Stops the workspace and keeps its state: creating the same id again picks up its sessions, history and disk files. RAM mounts are lost unless you snapshot first.

Cancel a workspace’s lines

POST /v1/workspaces/{id}/cancel Cancels the running and queued lines of every session, from HTTP, the CLI and SSH alike, and answers {"canceled": 2} once they have ended. The sessions stay open.

Kill a workspace’s jobs

POST /v1/workspaces/{id}/kill Kills every session’s background jobs (cmd &) and the processes runtimes started, and answers {"killed": 1}.

Clone a workspace

POST /v1/workspaces/{id}/clone
string
The new workspace’s id.
object
A config whose mounts replace the source’s.

Snapshot a workspace

GET /v1/workspaces/{id}/snapshot Answers the tar (application/x-tar). Secrets are stored redacted. The server never writes a snapshot to its own disk. While it captures, new lines and file writes wait, and running ones get 30 seconds to end; if any are still running then, it answers 409, so cancel them and retry. A clone waits the same way. POST /v1/workspaces/{id}/snapshot
string
required
Puts the tar in the server’s snapshot store under this key, and answers {id, key, size}. 400 when the server has none.

Load a snapshot

POST /v1/workspaces/load Upload the tar as multipart/form-data: a request part holding the JSON fields below, then a snapshot part of up to 1 GiB. Or send JSON with a key to load from the snapshot store.
string
The tar’s key in the snapshot store; leave it out when uploading.
string
The new workspace’s id.
object
A config that re-supplies the redacted credentials.

Sessions

Create a session

POST /v1/workspaces/{id}/sessions
string
The session id. A fresh one when absent.
string
A profile from the workspace’s profiles.
object
Prefix to mode (read, write, exec) to narrow those mounts, such as {"/data": "read"}.

List sessions

GET /v1/workspaces/{id}/sessions

Delete a session

DELETE /v1/workspaces/{id}/sessions/{session_id} Cancels its lines, kills its jobs and closes it.

Cancel a session’s lines

POST /v1/workspaces/{id}/sessions/{session_id}/cancel Cancels the session’s running and queued lines, from every way in, and answers {"canceled": 1} once they have ended. The session stays open.

Kill a session’s jobs

POST /v1/workspaces/{id}/sessions/{session_id}/kill Kills the session’s background jobs and the processes its runtimes started, and answers {"killed": 1}. The session stays open.

Change a session’s profile

PATCH /v1/workspaces/{id}/sessions/{session_id}
string | null
required
A profile from the workspace’s profiles; null for the workspace default.
Replaces the session’s modes, hides and rules. Its cwd, env and history stay.

VFS.md and SKILL.md

GET /v1/workspaces/{id}/vfs-md · /v1/workspaces/{id}/sessions/{session_id}/vfs-md PUT the same routes
string
GET only, on the workspace route: preview this profile.
string
required
PUT only: expose the document as a live, read-only file at this path.
Answers the document as text/markdown. skill-md in place of vfs-md serves SKILL.md. A missing session or parent is 404, a path that is taken 409, a bad path or profile 422.

Shell, VFS calls and tools

Each call acts as the session ?session_id= names, the default one when absent. ?explain=true on the shell or a VFS call answers what the call would do instead of doing it; see Explain.

Run a shell line

POST /v1/workspaces/{id}/shell
string
required
The line.
string
A working directory for this line only.
string
The session. The default when absent.
boolean
default:"false"
Answer 202 with {job_id, workspace_id, submitted_at} at once.
boolean
default:"false"
Answer the line’s explanation; run nothing.
Answers {kind, exit_code, stdout, stderr, refusal} when the line finishes, with the job id in X-Mirage-Job-Id. A dropped request cancels its job. With explain, only the line is read: stdin, cwd, runtime and background are refused. To send stdin, post multipart/form-data with a request part holding the JSON body, then a stdin part. The line starts when the stdin part begins and reads it as it arrives. A body that stops before its closing boundary cancels the line and answers 400. With ?background=true, the whole upload is read before the 202.

Call the VFS

POST /v1/workspaces/{id}/vfs/{call}
string
The session. The default when absent.
boolean
default:"false"
Answer the call’s explanation; run nothing.
The body is the call’s arguments, up to 4 MiB; bytes travel base64. Each call answers one field, or {}. A failed call answers {detail, errno}, and a policy’s refusal adds its refusal record. The errno picks the status: 404 for ENOENT and NO_XATTR, 403 for EACCES, EPERM and EROFS, 409 for EEXIST, ENOTEMPTY and EBUSY, 400 for ENOTDIR, EISDIR, EINVAL, EXDEV, ELOOP and ENOTSUP, and 500 for anything else, such as EIO.

Match paths

POST /v1/workspaces/{id}/glob
string
required
A pathname pattern, such as /src/**/*.py.
string
The session. The default when absent.
Answers {paths}, every path the session sees that matches.

Call a tool

POST /v1/workspaces/{id}/tools/{tool}
string
The session. The default when absent.
The agent tools; the body is the tool’s input: Answers {text, is_error}. A tool that fails is still 200, with is_error set.

Jobs

Every shell line is a job: pending, running, then done, failed or canceled.

List jobs

GET /v1/jobs
string
Only this workspace’s jobs.

Get a job

GET /v1/jobs/{job_id}

Wait for a job

POST /v1/jobs/{job_id}/wait
number
Seconds to wait. A timeout answers the job as it is, without canceling it.

Cancel a job

DELETE /v1/jobs/{job_id}

Asks

List asks

GET /v1/workspaces/{id}/asks
string
Only this session’s asks.
boolean
default:"false"
Include settled ones.

Answer an ask

POST /v1/workspaces/{id}/asks/{ask_id}
string
required
allow or deny.
string
default:"once"
once, or session to allow every matching line in the session.