> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP

> The Mirage server's HTTP routes, how to run it, and how it checks tokens.

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.

```bash theme={null}
curl -s http://127.0.0.1:8765/v1/workspaces \
  -H "Authorization: Bearer $(cat ~/.mirage/auth_token)"
```

## Run it

The server is a FastAPI app in Python and a Fastify app in TypeScript. Run it as your own service:

<CodeGroup>
  ```python Python theme={null}
  import uvicorn

  from mirage.server.app import build_app

  uvicorn.run(build_app(), host="127.0.0.1", port=8765)
  ```

  ```ts TypeScript theme={null}
  import { buildApp } from '@struktoai/mirage-server'

  await buildApp().listen({ host: '127.0.0.1', port: 8765 })
  ```
</CodeGroup>

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](/python/access/server) and [TypeScript](/typescript/access/server) server pages.

## Auth

Every route but `GET /v1/health` needs `Authorization: Bearer <token>`. `MIRAGE_AUTH_MODE` decides what the server accepts:

| Mode | Accepts | Configure |
| - | - | - |
| `local` (default) | the token in `~/.mirage/auth_token`, which the CLI writes when it starts the server | nothing |
| `token` | one fixed token | `MIRAGE_AUTH_TOKEN` |
| `jwt` | a JWT your issuer signed | `MIRAGE_JWT_PUBKEY` or `MIRAGE_JWT_PUBKEY_FILE`, and `MIRAGE_JWT_ALG` |

In `jwt` mode the server checks the signature against that public key, accepts only the pinned algorithm, requires `exp`, and checks `iss`, `aud` and `azp` when `MIRAGE_JWT_ISSUER`, `MIRAGE_JWT_AUDIENCE` and `MIRAGE_JWT_AUTHORIZED_PARTIES` are set. It never fetches keys, so a new key needs a restart.

<Warning>
  The server checks that a token is valid, not what it may do: any accepted token can use every route and every workspace, including `POST /v1/shutdown`. Give one server to one tenant. In `local` mode with no token in the environment or in `~/.mirage/auth_token`, the server asks for none.
</Warning>

## Errors

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

| Status | When |
| - | - |
| `400` | The body is not JSON or fails its schema, or the `Host` is not allowed. |
| `401` | A missing or rejected token. |
| `404` | An unknown workspace, session, job or ask. |
| `409` | A taken id, or an ask already answered. |
| `413` | A body over 4 MiB, or an uploaded snapshot over 1 GiB. |
| `422` | An unknown profile or a bad mode. |
| `499` | The job was canceled. |
| `500` | The job failed inside the server. |

## Server

### Check health

<Badge color="blue">GET</Badge> `/v1/health`

Answers `{"status": "ok", "workspaces": 2, "uptime_s": 12.5}`, without a token.

### Shut down

<Badge color="green">POST</Badge> `/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

<Badge color="green">POST</Badge> `/v1/workspaces`

<ParamField body="config" type="object" required>The [workspace YAML](/home/yaml) as JSON.</ParamField>
<ParamField body="id" type="string">The workspace id. A fresh one when absent.</ParamField>

Answers `201` and the workspace. The same id and config again answers `200` and that workspace; another config under a taken id answers `409`.

```bash theme={null}
curl -s http://127.0.0.1:8765/v1/workspaces \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"id": "demo", "config": {"mounts": {"/": {"vfs": "ram", "mode": "WRITE"}}}}'
```

### List workspaces

<Badge color="blue">GET</Badge> `/v1/workspaces`

### Get a workspace

<Badge color="blue">GET</Badge> `/v1/workspaces/{id}`

<ParamField query="verbose" type="boolean" default="false">Add cache and job internals.</ParamField>

### Delete a workspace

<Badge color="red">DELETE</Badge> `/v1/workspaces/{id}`

Closes the workspace and deletes its state.

### Clone a workspace

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/clone`

<ParamField body="id" type="string">The new workspace's id.</ParamField>
<ParamField body="override" type="object">A config whose mounts replace the source's.</ParamField>

### Snapshot a workspace

<Badge color="blue">GET</Badge> `/v1/workspaces/{id}/snapshot`

Answers the tar (`application/x-tar`). Secrets are stored redacted. The server never writes a snapshot to its own disk.

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/snapshot`

<ParamField body="key" type="string" required>Puts the tar in the server's [snapshot store](/home/snapshot#an-s3-like-store) under this key, and answers `{id, key, size}`. `400` when the server has none.</ParamField>

### Load a snapshot

<Badge color="green">POST</Badge> `/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.

<ParamField body="key" type="string">The tar's key in the snapshot store; leave it out when uploading.</ParamField>
<ParamField body="id" type="string">The new workspace's id.</ParamField>
<ParamField body="override" type="object">A config that re-supplies the redacted credentials.</ParamField>

## Sessions

### Create a session

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/sessions`

<ParamField body="session_id" type="string">The session id. A fresh one when absent.</ParamField>
<ParamField body="profile" type="string">A [profile](/home/permissions) from the workspace's `profiles`.</ParamField>
<ParamField body="mounts" type="object">Prefix to mode (`read`, `write`, `exec`) to narrow those mounts, such as `{"/data": "read"}`.</ParamField>

### List sessions

<Badge color="blue">GET</Badge> `/v1/workspaces/{id}/sessions`

### Delete a session

<Badge color="red">DELETE</Badge> `/v1/workspaces/{id}/sessions/{session_id}`

## Shell and tools

### Run a shell line

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/shell`

<ParamField body="command" type="string" required>The line.</ParamField>
<ParamField body="session_id" type="string">The session. The default when absent.</ParamField>
<ParamField body="cwd" type="string">A working directory for this line only.</ParamField>
<ParamField query="background" type="boolean" default="false">Answer `202` with `{job_id, workspace_id, submitted_at}` at once.</ParamField>

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.

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`.

```bash theme={null}
curl -s http://127.0.0.1:8765/v1/workspaces/demo/shell \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"command": "ls / | wc -l"}'
```

### Call a tool

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/{tool}`

<ParamField query="session_id" type="string">The session. The default when absent.</ParamField>

The body is the tool's input:

| Tool | Required | Optional |
| - | - | - |
| `read` | `path` | `offset`, `limit` |
| `write` | `path`, `content` | |
| `edit` | `path`, `old_string`, `new_string` | `replace_all` |
| `ls` | `path` | |
| `grep` | `pattern`, `path` | `ignore_case`, `fixed_strings`, `include`, `context`, `files_with_matches`, `count`, `max_count` |
| `glob` | `pattern` | `path` |

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

<Badge color="blue">GET</Badge> `/v1/jobs`

<ParamField query="workspace_id" type="string">Only this workspace's jobs.</ParamField>

### Get a job

<Badge color="blue">GET</Badge> `/v1/jobs/{job_id}`

### Wait for a job

<Badge color="green">POST</Badge> `/v1/jobs/{job_id}/wait`

<ParamField body="timeout_s" type="number">Seconds to wait. A timeout answers the job as it is, without canceling it.</ParamField>

### Cancel a job

<Badge color="red">DELETE</Badge> `/v1/jobs/{job_id}`

## Asks

### List asks

<Badge color="blue">GET</Badge> `/v1/workspaces/{id}/asks`

<ParamField query="session_id" type="string">Only this session's asks.</ParamField>
<ParamField query="all" type="boolean" default="false">Include settled ones.</ParamField>

### Answer an ask

<Badge color="green">POST</Badge> `/v1/workspaces/{id}/asks/{ask_id}`

<ParamField body="answer" type="string" required>`allow` or `deny`.</ParamField>
<ParamField body="scope" type="string" default="once">`once`, or `session` to allow every matching line in the session.</ParamField>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.