API · Endpoints

Doc body

Read and write a doc-mode workspace's body. Send Markdown OR ProseMirror JSON; reads always return ProseMirror JSON. The append endpoint saves the fetch+merge+PUT round-trip for agents producing content in chunks.

Generated from src/lib/api-paths/doc.ts. The body schemas come from UpdateDocSchema and AppendDocSectionSchema in the runtime.

get/api/workspaces/{slug}/doc

Read the doc body

Returns the workspace's doc body as ProseMirror JSON. 403 if the workspace is in table mode.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Query parameters

NameTypeRequiredDescription
surfacestringnoSurface slug for multi-doc workspaces. Omit for the primary doc surface.
format"json" | "markdown" | "text"noResponse shape: `json` (ProseMirror, default) · `markdown` (CommonMark + GFM, useful for LLM context or textual diff) · `text` (plain text, no formatting marks). Overrides `Accept` header.

Responses

StatusBodyDescription
200objectDoc body.
403ErrorWorkspace is in table mode (no doc surface).

put/api/workspaces/{slug}/doc

Replace the doc body

Send either `content` (ProseMirror JSON) or `markdown` (string). When both are present, `content` wins. Markdown gets converted server-side via remark + remark-gfm + Dock's rich format set (mermaid, math, callouts, cross-refs, embeds).

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
contentobjectno
markdownstringno

Responses

StatusBodyDescription
200objectReplaced.
400ErrorBody shape failed validation, OR doc body exceeded byte/depth/node-count cap.

post/api/workspaces/{slug}/doc/sections

Append a markdown section to the doc

Append-only counterpart to PUT /doc. Saves the round-trip cost of fetch+merge+PUT for agents producing content in chunks (changelog updates, daily standups, ingest pipelines).

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
markdownstringyes

Responses

StatusBodyDescription
200objectAppended.

patch/api/workspaces/{slug}/doc/sections

Replace a single section by heading

Targeted-edit counterpart to PUT /doc (full replacement) and POST /doc/sections (append). Finds the FIRST heading whose plain text matches `heading` (case-sensitive, trimmed), then replaces everything from that heading up to the next heading at the same or shallower level. `markdown` is the FULL replacement INCLUDING the heading line; pass an empty string to delete the section. Returns 404 when no matching heading exists, by design, so a misremembered heading fails loudly. Same byte/depth/node-count guard as PUT /doc; same writeDocBody attribution path.

Auth: Bearer token (API key or OAuth access token).

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
headingstringyes
markdownstringyes

Responses

StatusBodyDescription
200objectReplaced.
400ErrorBody shape failed validation, OR doc body exceeded byte/depth/node-count cap after the section was substituted.
404ErrorNo heading on the doc matches `heading` exactly.

Frequently asked questions

How do I read a Dock doc body via the REST API?
GET `/api/workspaces/:slug/doc`. Returns ProseMirror JSON by default; pass `?format=markdown` for CommonMark + GFM, or `?format=text` for plain text. Multi-doc workspaces accept `?surface=<slug>` to target a specific doc tab.
How do I write a Dock doc body via the REST API?
PUT `/api/workspaces/:slug/doc` with `{ markdown: "…" }` (recommended) or `{ content: { type: 'doc', content: [...] } }` (ProseMirror JSON). Markdown is converted server-side via remark + GFM + Dock's rich format set.
How do I append to the end of a Dock doc body without overwriting?
POST `/api/workspaces/:slug/doc/sections` with `{ markdown: "…" }`. Saves the round-trip cost of GET-merge-PUT for crons + ingest agents producing content in chunks (changelog updates, daily summaries).
How do I replace just one section of a Dock doc by heading?
PATCH `/api/workspaces/:slug/doc/sections` with `{ heading, markdown }`. Finds the FIRST heading whose text matches `heading` (case-sensitive), replaces from that heading to the next heading at the same or shallower level.
What Markdown features does Dock's doc body API support?
CommonMark + GFM + Mermaid diagrams + KaTeX math + GFM-style callouts (NOTE/TIP/IMPORTANT/WARNING/CAUTION) + sanitized SVG embeds + collapsible details + cross-references (`[[slug]]` / `[[slug#tab]]` / `[[org/slug]]`) + @-mentions of users and agents (`[@Label](dock:mention/<kind>/<id>)`) + oEmbed (YouTube/Vimeo/Loom/Figma/CodePen/gists).
How do I @-mention a user or agent from an agent's `update_doc` Markdown?
Use the markdown link form `[@Label](dock:mention/<kind>/<id>)` where `<kind>` is `agent` or `human` and `<id>` is the principal id. Examples: `[@Argus](dock:mention/agent/agt_abc?org=vector)` or `[@Sarah](dock:mention/human/usr_xyz)`. The dock parser detects the `dock:mention/` href prefix and emits an atomic mention chip; non-Dock markdown readers see a plain `@Label` link. Mentioning a human triggers an inbox row + email; mentioning an agent fires the `doc.mention_added` webhook. Re-saving a doc that already mentions someone does not re-fire, only newly-added mentions notify.
How do I validate Dock doc Markdown before writing?
MCP `validate_doc_markdown(markdown=…)` returns `{ ok, errors, warnings, parsed }` with parsed counts per format type (including `imageCount` + `videoCount`). Pure parse + analysis, never writes. Useful when iterating on rich-format content.
How do I embed an image in a Dock doc?
Standard CommonMark `![alt](https://…)` syntax. Any publicly-reachable HTTPS URL works; `data:` URIs are rejected (`allowBase64: false`). For user-uploaded images, hit `POST /api/workspaces/:slug/upload` (10 MB cap, multipart) to get a Vercel Blob URL, then reference it. Renders block-feeling via CSS even though the underlying node is inline-grouped. Per-doc cap: 200 images.
How do I embed a 4K video in a Dock doc?
A lone URL on its own paragraph ending in `.mp4`, `.m4v`, `.webm`, `.mov`, or `.mkv` becomes a native HTML5 `<video controls>` player. No iframe, no transcoding, no quality loss. For user-uploaded files, hit `POST /api/workspaces/:slug/upload-media` to mint a Vercel Blob client-upload token, then push the file with `@vercel/blob/client` `upload()`, 5 GB per file, multipart streaming, bypasses the function-body cap. Per-doc cap: 20 videos.
What's the size cap on a Dock doc body?
Per-format caps: max 200 images, max 20 videos (5 GB per file), max 50 Mermaid diagrams (30KB source each), max 500 math expressions (8KB each), max 50 SVG blocks (100KB each post-sanitize), max 200 cross-refs, max 20 embeds. Whole-doc cap on byte/depth/node-count enforced server-side; real prose never trips it.
How does Dock handle simultaneous doc edits?
By default, last-write-wins on doc body level (no CRDT yet). Two concurrent PUTs both return 200 and the later one's content survives, so multi-agent loops need to opt into one of two safer patterns: (1) `update_doc_section` for heading-scoped edits, different agents own different sections, parallel-safe; (2) `If-Unmodified-Since` precondition on full-body writes, pass the `updatedAt` you read via `GET /doc` (or `get_doc`); the server 409s if the doc has moved on. See the next two questions for the wire format.
How do I prevent two agents from clobbering each other's Dock doc writes?
Send `If-Unmodified-Since: <RFC 7231 timestamp>` on `PUT /api/workspaces/:slug/doc`. The value is the `updatedAt` you got from a prior `GET /doc` (or any `doc.updated` event). When the doc has moved on since your cutoff, the server responds 409 Conflict with `{ code: "conflict", message, details: { currentUpdatedAt, precondition } }` so your client can refetch + merge + retry. Without the header, the write is last-write-wins (back-compat preserved). The MCP `update_doc` tool accepts the same precondition via the `if_unmodified_since` arg, returning `-32602` with `data: { conflict: true, currentUpdatedAt, precondition }` on stale writes. Learn more →
What's the recipe for safe multi-agent Dock doc co-authoring?
Two patterns, pick per write. **Section-scoped (preferred when ownership is clear):** each agent owns a different heading-bounded section, call `update_doc_section(heading, markdown)` instead of `update_doc(markdown)`. Concurrent section writes never collide because each one targets only its own section. Cheapest mental model + no extra round-trips. **Whole-body with optimistic concurrency:** when two agents really need to edit the same section (or the doc has no headings), GET the doc, read `updatedAt`, send the new body with `If-Unmodified-Since: <updatedAt>` (REST) or `if_unmodified_since: "<ISO>"` (MCP). On 409, refetch + merge + retry. The substrate is whole-body PUT either way (no CRDT yet), so it's on the caller to opt into the precondition. Without it, agents silently clobber each other.
Can my AI agent get notified when a Dock doc is updated or @-mentions it?
Yes. Subscribe to `doc.updated` for any body change, `doc.heading_added` for outline-level signals, or `doc.mention_added` to wake up only when newly @-mentioned. The mention payload carries `mentions: [{ kind: "user" | "agent", id, label }]` for newly-added mentions only. Every event includes the `principal` block so the agent can route per-author. The `comment.added` webhook covers the comment-side equivalent, same `mentions` array shape. Learn more →
How do I get the previous version of a Dock doc body?
Doc-body history is on the roadmap. Today: every change emits an event with the diff context, so you can reconstruct prior versions from the event stream. Or, use the workspace's audit log via `GET /api/workspaces/:slug/events`.
Updated