File and Shell Tools
Four tools cover file content over the API: read, write, edit and bash. Paths are workspace-absolute (/Projects/auth.py), Monofile versions every write, and the writes of one call land in one changeset. The request envelope is the same for all of them, see Calling Tools.
curl -X POST "https://www.monofile.ai/api/tools/read" \
-H "Authorization: Bearer $MONOFILE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path": "/Projects/auth.py", "includeMetadata": true}'Files
read
Returns a file's raw content, optionally with its metadata.
| Input | Type | Required | Description |
|---|---|---|---|
path | string | yes | Full path to the file |
includeMetadata | boolean | no | Add id, name, path, mimeType, language, size, lines and modified. Default false |
Scope: files:read. Ask for metadata when you plan to edit: modified is the timestamp you pass back as expectedUpdatedAt.
{ "path": "/Projects/auth.py", "includeMetadata": true }write
Creates a file, or overwrites one that already exists. Monofile keeps the previous content as a version, so you can restore it after an overwrite.
| Input | Type | Required | Description |
|---|---|---|---|
path | string | yes | Full path to create or replace |
content | string | yes | The new content |
createParents | boolean | no | Create missing parent folders. Default true |
Scope: files:write. Two file types have their own tools because their content is machine-generated JSON. Use create_diagram for .draw and canvas_edit for .canvas, both on Content Tools.
{ "path": "/Projects/notes/plan.md", "content": "# Plan\n\n- [ ] draft\n" }edit
Replaces an exact string inside a file. Prefer it to write on an existing file, because it changes one region and leaves the rest of the document alone.
| Input | Type | Required | Description |
|---|---|---|---|
path | string | yes | Full path to the file |
old_string | string | yes | The exact text to find, case-sensitive |
new_string | string | yes | The replacement. Empty deletes the matched text |
replace_all | boolean | no | Replace every occurrence. Default false |
dryRun | boolean | no | Preview without writing. Default false |
showDiff | boolean | no | Include a unified diff in the response. Default true |
expectedUpdatedAt | string | no | ISO timestamp from a prior read. Refuses the edit if the file changed since |
Scope: files:write.
{
"path": "/Projects/auth.py",
"old_string": "def login(user):",
"new_string": "def login(user, remember=False):"
}The edit contract
The tool matches old_string literally. No regular expressions, no whitespace normalization, no case folding. Most failed calls trip on that, and each failure has its own code:
| Error | What happened | What to do |
|---|---|---|
NO_MATCH | old_string is not in the file | Read the file again. Your copy is stale, or the indentation or line endings differ from what you assumed |
MULTIPLE_MATCHES | More than one occurrence and replace_all is false. The message carries the count | Extend old_string with surrounding lines until it is unique, or set replace_all: true if you really mean all of them |
EDIT_CONFLICT | You passed expectedUpdatedAt and the file changed after that timestamp | Re-read, rebuild old_string from the new content, retry |
INVALID_INPUT | old_string is empty, or equal to new_string | Send a non-empty search string that differs from the replacement |
Shell
bash runs a command against your workspace. Read-only commands answer instantly. Everything else needs a real machine, so it runs in a sandbox, and the sandbox is part of paid plans.
The two tiers
The built-in shell is on every plan. A read-only chain of the commands below, joined by ;, && or ||, runs in-process straight against storage. It is instant and always sees the latest content.
| Command | Flags it reads |
|---|---|
pwd | none |
ls | -l, -a, -h |
tree | -L N, --depth N |
cat | -n |
head / tail | -n N |
stat | none |
wc | -l, -w, -c |
find | -name GLOB, -type f or -type d, -maxdepth N, -mtime N, -size N. Predicates are AND-ed, there is no -not or -o |
grep | -i, -n, -l, -c, -v, -w, -A N, -B N, -C N, --include=GLOB, --exclude=GLOB. Always recursive, JavaScript regex, line numbers on by default |
mkdir | -p |
rm | -r, -R, -f |
mv / cp | cp takes -r, -R |
touch | none |
echo / printf | redirection with > and >>. printf handles %s, \n, \t |
One path per command, no glob expansion, and flag values are space-separated (-n 5, not -n5). This tier has no pipes, <, 2>, command substitution, background &, sed, awk, sort or interpreters.
The full shell is part of paid plans (Pro on a personal workspace, Team on an org workspace). Anything that is not a read-only built-in chain runs in a real sh with real coreutils, inside a persistent per-workspace sandbox whose disk mirrors your files. That is where pipes, redirects, sed, awk, sort, jq, git, python3, node and multi-line scripts work. Monofile writes the file changes the command leaves behind into the workspace as versioned edits, one changeset per call.
bash
| Input | Type | Required | Description |
|---|---|---|---|
command | string | yes | The command to run |
timeout | number | no | Full shell only. Seconds before the command is killed. Default 60, larger values are clamped to 240 |
cwd | string | no | Full shell only. Workspace-absolute working directory without .. segments. Default / |
debug | boolean | no | Full shell only. Adds sandbox and usage detail to the result |
The scope a command needs follows from what it does. Reading needs files:read. mkdir, mv, cp, touch and echo/printf with > need files:write. rm needs files:delete. The full shell checks the write-back diff, so a missing scope rejects the whole diff and writes nothing.
{"command": "grep -rn \"TODO\" /Projects --include=*.ts"}curl -X POST "https://www.monofile.ai/api/tools/bash" \
-H "Authorization: Bearer $MONOFILE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"command": "grep -rn \"TODO\" /Projects --include=*.ts"}'grep and find exit 1 when they match nothing. Treat that as an empty result.
What the result tells you
A built-in run returns output, exitCode and, on failure, stderr. A full-shell run adds these fields:
| Field | Meaning |
|---|---|
changes | created, updated, deleted and renamed paths the run wrote back |
conflicts | Files that changed elsewhere while your command ran. Monofile kept your version, and the one it replaced is in that file's versions |
skipped | Paths the command touched but Monofile did not write back, each with a reason such as symlink, binary, too-large or media |
warnings | Readable notes for the timeout, conflicts and skips above |
truncated, timedOut | Output hit the cap of 400 lines or 30 KB, or the command was killed. Changes made before a kill are still written back |
timings | Where the milliseconds went. Always present |
Errors you can plan for: SANDBOX_BUSY (one command per workspace at a time, retry), RATE_LIMITED, DIFF_TOO_LARGE (more than 200 changed files), INSUFFICIENT_CREDITS, SANDBOX_UNAVAILABLE and TIMEOUT.
Without the paid tier
When a workspace is not entitled to the sandbox, any command the built-in shell knows still runs there, so listing, reading, searching and basic file management keep working. A pipe, a script or an interpreter needs the sandbox, so it comes back as SANDBOX_NOT_ENTITLED, and the message names the built-in alternatives.
GET /api/tools describes the bash tool as your own key sees it, including which tier it has.
Next steps
Calling ToolsThe one-endpoint pattern, the MCP-shaped envelope, and the index of every tool.
Content ToolsCanvas, diagram, version and checkpoint tools.
Errors and LimitsEvery error code, what causes it, and how to recover.
Filesystem APIThe manifest and batch endpoints for bulk sync clients.