# Reference: workspace API > Neutral, exhaustive description of the endpoints behind the workspace panel and the composer's selectors: the file API, the skills endpoint and the terminal WebSocket. Relative paths resolve against the server's `--cwd`; absolute paths are used as-is (access is unrestricted by design — ori targets already-isolated sandboxes). ## GET /api/files Lists a directory. | Query parameter | Type | Default | Description | | --- | --- | --- | --- | | `path` | string | the workspace root | Directory to list. | Response `200`: ```json { "path": "/work", "entries": [ { "name": "src", "path": "/work/src", "isDir": true, "size": 0 } ] } ``` Entries are sorted directories first, then files, each group alphabetically. ## GET /api/files/search Searches files recursively under the workspace root, for the composer's `@` selector. | Query parameter | Type | Default | Description | | --- | --- | --- | --- | | `q` | string | `""` | Case-insensitive substring matched against the path relative to the root. Empty returns the first files. | | `limit` | integer | 50 | Maximum number of hits; capped at 500. | Response `200`: ```json { "root": "/work", "files": [ { "name": "main.go", "path": "/work/src/main.go", "relPath": "src/main.go" } ] } ``` `path` is absolute (usable in the other endpoints), `relPath` uses forward slashes. Hits are ranked: files whose base name contains the query first, then shorter relative paths, then alphabetically. The walk never enters `.git` or `node_modules`, skips unreadable entries, and stops after 50 000 directory entries. `files` is always an array (`[]` when nothing matches). A missing root is a `404`. ## GET /api/file Reads a text file. | Query parameter | Type | Default | Description | | --- | --- | --- | --- | | `path` | string | — | File to read. | Response `200`: `{ "path": "/work/README.md", "content": "…" }` ## PUT /api/file Writes a text file, creating parent directories as needed. | Query parameter | Type | Default | Description | | --- | --- | --- | --- | | `path` | string | — (required) | File to write. | Body: `{ "content": "…" }` — response `200`: `{ "path": "…", "status": "saved" }` ## GET /api/raw Streams a file's bytes as-is — how the preview pane shows images. No size cap, no text requirement; `Range` requests are honoured (`http.ServeContent`). | Query parameter | Type | Default | Description | | --- | --- | --- | --- | | `path` | string | — (required) | File to stream. | Response `200` with the file bytes, `X-Content-Type-Options: nosniff`, and a `Content-Type` chosen in this order: | Extension | Content-Type | | --- | --- | | `.png` `.jpg` `.jpeg` `.gif` `.webp` `.svg` `.bmp` `.ico` `.avif` | pinned image types (`image/png`, `image/jpeg`, `image/gif`, `image/webp`, `image/svg+xml`, `image/bmp`, `image/x-icon`, `image/avif`) | | any other known extension | the host's mime table (e.g. `text/markdown; charset=utf-8`) | | unknown | sniffed from the first 512 bytes | ## GET /api/skills Lists the Claude Code skills available to the session, for the composer's `/` selector. Response `200`: ```json { "skills": [ { "name": "quality", "description": "Audit code quality with qlty.", "path": "/work/.claude/skills/quality/SKILL.md", "source": "project" } ] } ``` A skill is a directory holding a `SKILL.md`, looked up in `/.claude/skills/*/` (`source: "project"`) and `~/.claude/skills/*/` (`source: "user"`). `name` and `description` come from the file's YAML frontmatter (`key: value`, quoted values and `>` / `|` block scalars are understood); a missing name falls back to the directory name, a missing description to `""`. A project skill shadows a user skill of the same name. Results are sorted by name; `skills` is always an array. ## File API errors All errors are JSON: `{ "error": "message" }`. | Status | Cause | | --- | --- | | 400 | Missing `path` on PUT or `GET /api/raw`, invalid JSON body, or reading a directory. | | 403 | Filesystem permission denied. | | 404 | Path (or the workspace root, for the search) does not exist. | | 413 | File larger than 5 MiB (`GET /api/file` only; `/api/raw` has no cap). | | 415 | File is not valid UTF-8 text (`GET /api/file` only). | ## GET /ws/terminal Upgrades to a WebSocket and starts one shell (from `$SHELL`, falling back to `bash` then `sh`, with `TERM=xterm-256color`) inside a pseudo-terminal, in the server's `--cwd`. | Direction | Frame type | Content | | --- | --- | --- | | server → client | binary | Raw terminal output bytes. | | client → server | text | `{"type":"input","data":"…"}` — keystrokes written to the pty. | | client → server | text | `{"type":"resize","cols":N,"rows":N}` — viewport change. | The socket closes with a normal-closure status when the shell exits; closing the socket kills the shell. Malformed text frames are ignored.