apply_document_operations Edit documents atomically. append_blocks appends at the tail using content:{markdown:...}. Append-only merge_non_overlapping may omit expectedRevision. Insert: {"type":"insert_blocks","afterId":null,"content":{"markdown":"# Notes"}}. Use content; afterId:null initializes or prepends. documentId selects a page; omit for tab page. Edit pages before blocks. replace_block/delete_blocks need expectedVersion; merge_non_overlapping allows unrelated edits. replace_document needs exact revision and enabled deletes. Reuse revisionAfter/blockVersions; dryRun validates only. Retry identically. Details: /docs/mcp/apply_document_operations. Append without reads: operations:[{type:append_blocks,content:{markdown:...}}], conflictPolicy=merge_non_overlapping; omit expectedRevision for append-only batches. The server resolves the current tail and revision atomically. CLI: qraft doc append SHEET --markdown @file. Markdown content is an object: content:{markdown:"..."}; do not put markdown at operation level or add a content.format field. Initialize an empty document with insert_blocks and afterId:null. This works when agent deletes are disabled; replace_document is destructive even for an empty document and is blocked then. Use your actual boardId, sheetId, current sheet revision from read_board/list_sheets, and a fresh requestId. For hosted documents also include documentId. Complete insertion example: { "boardId": "fixture_board", "sheetId": "sheet_notes", "requestId": "initialize_notes_20260922", "expectedRevision": "g1.r0", "operations": [ { "type": "insert_blocks", "afterId": null, "content": { "markdown": "# Research\n\nEvidence-based observations." } } ] } For a small document edit, use known block IDs + versions from read_board(includeVersions:true, blockIds or section) or search_board, then replace_block with conflictPolicy=merge_non_overlapping. Do not export/replace the whole document for a paragraph edit. Carry revisionAfter and blockVersions into subsequent edits on this sheet; null versions mean deletion. Versions describe the committed result, are absent for dryRun, and replays return the original versions (which may now be stale). On conflict, reread only the affected blocks and necessary context, reconsider, then use a NEW requestId with retryOfRequestId pointing to the failed request. Edit a document tab atomically using Markdown or structured rich-text blocks. Supply sheetId, expectedRevision and a unique requestId. read_board returns top-level block IDs and versions. replace_block and delete_blocks require those versions; CLI qraft apply fails locally for missing guards with a focused qraft read --versions instruction, preserving supplied stale guards; merge_non_overlapping allows unrelated block changes. insert_blocks uses a stable afterId (null inserts at the beginning). replace_document requires the exact current revision. Images use stable private /api/boards/{boardId}/assets/{sha256} references. Reuse src from a full block read. For new images, POST raw PNG/JPEG/WebP/GIF bytes (max 8 MiB) to /api/v1/boards/{boardId}/assets and use the returned src, or supply a data URL once; commits automatically move it to R2. Never repeat image bytes in later edits. Cross-board reuse requires source read access and copies the asset. Exports inline images for portability. Tab links use /board/{boardId}?sheet={sheetId}; a page link adds &page={pageId}. Pages: a Document tab can hold several pages; read_board lists them as pages[{id,title,order}]. The tab's own page has id = sheetId and is edited by omitting documentId; every other page is edited with documentId = its id. add_page {page:{id,title},afterPageId?,content?} creates a page (caller-assigned id; afterPageId null puts it first, omitted appends; content takes the same Markdown/blocks shape). rename_page and remove_page need the page's current title as expectedTitle; reorder_page takes afterPageId. Page operations run before block operations in the same command, so one call can add a page and write into it with documentId set to the new page. The tab's own page cannot be removed. remove_page is destructive and blocked when agent deletes are disabled; revert_change restores a removed page with its content. dryRun validates without saving. Retry ambiguous outcomes with the identical requestId and payload. Charts in Journals and Documents: use a fenced qraft-chart code block containing JSON, or a Document codeBlock with attrs.language="qraft-chart" and a text child containing that JSON. Snapshot shape: {version:1,type:"bar"|"line"|"pie"|"donut",title:string,xLabel?:string,unit?:string,notes?:string,series:[{name:string}],rows:[{label:string,values:[number|null]}]}. One value per series per row; all series share unit. Blank measurements are null, not zero. Limits: 500 rows, 8 series, 64 KB, and the host content limit. Pie/donut requires exactly one series, nonnegative nonmissing values, positive finite total. No vendor options or callbacks. Example: {version:1,type:"bar",title:"p95 frame time",unit:"ms",series:[{name:"p95"}],rows:[{label:"Before",values:[97]},{label:"After",values:[16.7]}]}. Document append_blocks/replace_block accept content.markdown or content.blocks; guard replace/delete by block version. Journal add_entry/update_entry uses fenced JSON in body; update_entry needs expected.body and revision. Overview/compact reads summarize charts; read the entry whole or Document block with view=full for lossless data. CLI: qraft chart encode spec.json --format markdown|block|json; qraft chart append spec.json --sheet S [--entry-id E | --document-id PAGE]. Markdown/JSON retain source; static HTML/PDF show readable data-table fallbacks.