update_doc_section
Replace a single section of a workspace's doc body, identified by its heading text. The targeted edit complement to update_doc (full replacement) and append_doc_section (append-only at the end). Use this when the agent maintains a recurring section (e.g., a 'Status' block in a launch-prep doc, an 'Outcomes' block in a meeting note) and only needs to refresh that one piece. Without it, agents are forced into 'GET → splice → PUT' which costs tokens, costs latency, and races against any concurrent human edit elsewhere in the doc (last-write-wins clobbers). Section semantics: the FIRST heading whose plain text matches heading exactly (case-sensitive on trimmed text) is found, and everything from that heading up to the next heading at the same OR shallower level is replaced. So a ## Outcomes section ends at the next ## … or # …; nested ### … subsections stay part of the replaced range. Returns 404 when no matching heading exists; strict by design so a misremembered heading fails loudly. markdown is the FULL replacement, INCLUDING the heading line: pass it back as-is to keep the heading, change it to rename or rewrite the heading, change the heading level, or omit the heading entirely (collapses the section into the prior one). Empty markdown deletes the section. Same markdown surface as update_doc / append_doc_section (CommonMark + GFM +  images + lone-URL videos (mp4/webm/mov/mkv/m4v) + Mermaid + KaTeX + callouts + SVG + details + cross-refs + @-mentions + URL embeds). Identity / attribution / events / doc-guard all flow through the same writeDocBody path as the other doc endpoints, so @-mentions in the new section fire doc.mention_added for newly-added mentions just like update_doc does. Requires editor role. Multi-surface workspaces optionally accept surface_slug to target a specific doc surface. WARNING — a section runs to the next heading of the SAME OR SHALLOWER level, so targeting the LAST heading (or a lone H1) means its section extends to the END OF THE DOCUMENT and this call replaces everything below it. That has silently destroyed a doc twice (#8196), both times returning success. Call get_doc first and check which headings follow the one you are targeting. Agent writes that would drop most of the doc's blocks are now REFUSED with both block counts. So are writes that would delete a heading inside the replaced range (a nested subsection, content below a trailing heading, or the target heading itself when you send only body text), drop a callout / quote / code block / table / image / embed in the range, or most of the section's own blocks; the refusal lists removedHeadings and removedBlocks. A block counts as carried back when one top-level block of your markdown has exactly its text (whitespace aside), or when you edit it in place (same number of blocks, same type and shape at the same position, and not emptied); rewording a callout into a paragraph, or folding its text into a larger block, counts as removing it. Carry back what you mean to keep, or pass allowBlockLoss: true to confirm an intended deletion. Edited or deleted paragraphs and lists are allowed but reported: replacedSection.removedBlockCount and removedBlocks. CONCURRENCY: concurrent writes to other sections are merged, never overwritten. But if you rebuild a section from an earlier read (someone may have written since), pass the updatedAt from your get_doc as if_unmodified_since, or pass the sectionHash from your previous update_doc_section response as if_section_hash (guards only this section). Without a precondition the write wins even over an edit you never saw. Every success returns prior (the replaced range's updatedBy, updatedAt, blockCount, contentHash: who and what you overwrote), sectionHash (the section as it now stands, the value for your next if_section_hash; null if your markdown left no heading), and the new updatedAt to chain into your next if_unmodified_since. Heed warning (section_recently_changed_by_other): your unguarded write landed right after another principal's; updatedBy on the doc names only the latest writer, so re-read and restore their edit if your body predates it.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes | The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace'). | |
| heading | Yes | Plain text of the heading to find (case-sensitive, trimmed). For `## Outcomes`, pass `Outcomes`. Hash marks and surrounding whitespace are stripped from the comparison automatically by the markdown converter. Use `get_doc` first if you need to enumerate the headings actually present. | |
| markdown | Yes | FULL replacement markdown for the section, including the heading line if you want to keep / rename / restructure it. Empty string deletes the section. | |
| surface_slug | No | Optional doc surface slug for multi-doc workspaces. Omit to target the primary doc surface. | |
| allowBlockLoss | No | Confirm a replacement that removes content. Omit it normally: the call is refused (with block counts, any `removedHeadings` and any `removedBlocks`) when it would destroy the bulk of the doc or of the section, delete a heading inside the replaced range that your markdown does not carry back, or drop a callout, quote, code block, table, image, embed, diagram, math or details block in the range. Set true only when the deletion is what you intend. | |
| if_section_hash | No | Optional precondition scoped to THIS section: the `sectionHash` from your last update_doc_section response for this section (the hash AFTER that write; `prior.contentHash` is the hash before it and would always conflict). When the section has changed since, the write is rejected with `code: -32602` and `data: { conflict: true, currentSectionHash }`; edits to other sections don't trip it. Never retried; re-read, merge, and resend with the new hash. | |
| if_unmodified_since | No | Optional precondition. ISO 8601 timestamp (typically the `updatedAt` you read via `get_doc`, or the `updatedAt` of your last write). When set and the doc has changed since, the write is rejected with `code: -32602` and `data: { conflict: true, currentUpdatedAt, precondition }` so you can refetch and rebuild the section instead of overwriting someone else's edit with a stale copy. Compared to the millisecond when the value carries milliseconds. Concurrent writes to OTHER sections are always safe without it; pass it whenever your replacement was built from content you read earlier. |