Edit a file (live)
apply_edits
| Through | Form |
|---|---|
| HTTP | PATCH https://api.markdownbrain.ai/v1/file |
| MCP | tool apply_edits |
| CLI | mdbrain call apply_edits |
Edit a file's markdown by search-and-replace, applied live: the change lands in the document anyone viewing the file is reading, not just in stored text. Name the file with path (its workspace-relative address, e.g. "specs/44-front-matter.md") or with file_id — exactly one of the two. Each edit locates old_text in the CURRENT content and replaces it with new_text; it must occur exactly once, unless the edit sets replace_all: true, which replaces every occurrence and reports how many as count in landed. All-or-nothing by default: if any old_text can't be located, NOTHING is applied (pass partial: true for best-effort). Pass base_version (the version from the read_file that you derived these old_text strings from) to be told, instead of guessing, when the document changed underneath you — the call is then rejected without applying anything. The response carries the resulting version and where each edit landed, so you can confirm the outcome without re-reading. Editing the brain's root brain-config.yaml, which only the brain's keepers can, takes effect at once: the response also carries compile — armed (how many triggers and session caps are now stored), problems (each with its line), unarmed (triggers that compiled to nothing, and why), missing_agents (declared agents a person still has to set up) and notices (what your change did that a person would want to know). A save is never refused because of what it says. Link other documents as [title](/path.md), rooted at the brain.
Who may call it: agents and people.
Writes: yes.
Arguments
| Name | Type | Required | Travels in | Description |
|---|---|---|---|---|
file_id | string | no | JSON body | file_node id of the file to edit — or use path |
path | string | no | JSON body | Workspace-relative path of the file to edit, e.g. "specs/44-front-matter.md" |
workspace_id | string | no | JSON body | Only needed to disambiguate a path that exists in more than one workspace you can see |
edits | array of object | yes | JSON body | Applied in order; each resolves against the content left by the previous |
base_version | string | no | JSON body | The version from the read_file these edits were built from. If it no longer matches, the call is rejected with VERSION_MISMATCH and nothing is applied. |
partial | boolean | no | JSON body | Default false (all-or-nothing). true = apply whatever matches and report the rest. |
Success
200 with application/json.
Example request
curl -X PATCH https://api.markdownbrain.ai/v1/file \
-H "Authorization: Bearer $MDBRAIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"path":"specs/12-onboarding.md","workspace_id":"00000000-0000-4000-8000-000000000010","base_version":"Vn3kR8f1aQ2bC4dE5fG6hI","edits":[{"old_text":"Each invitation expires after seven days.","new_text":"Each invitation expires after fourteen days."}]}'
mdbrain call apply_edits \
--path specs/12-onboarding.md \
--workspace-id 00000000-0000-4000-8000-000000000010 \
--base-version Vn3kR8f1aQ2bC4dE5fG6hI \
--edits '[{"old_text":"Each invitation expires after seven days.","new_text":"Each invitation expires after fourteen days."}]'
Example response
Written from the code rather than captured from a call.
{
"file_id": "00000000-0000-4000-8000-000000000102",
"applied": 1,
"skipped": [],
"version": "Qp7sT2uV9wX4yZ1aB3cD5e",
"landed": [
{
"old_text": "Each invitation expires after seven days.",
"index": 112
}
]
}
Refusals
A refusal is a status code with { error: { code, message } }. See Errors.