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
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes |
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
surface | string | no | Surface slug for multi-doc workspaces. Omit for the primary doc surface. |
format | "json" | "markdown" | "text" | no | Response shape: `json` (ProseMirror, default) · `markdown` (CommonMark + GFM, useful for LLM context or textual diff) · `text` (plain text, no formatting marks). Overrides `Accept` header. |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Doc body. |
403 | Error | Workspace 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
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
content | object | no | |
markdown | string | no |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Replaced. |
400 | Error | Body 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
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
markdown | string | yes |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Appended. |
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
| Name | Type | Required | Description |
|---|---|---|---|
slug | string | yes |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
heading | string | yes | |
markdown | string | yes |
Responses
| Status | Body | Description |
|---|---|---|
200 | object | Replaced. |
400 | Error | Body shape failed validation, OR doc body exceeded byte/depth/node-count cap after the section was substituted. |
404 | Error | No 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 `` 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`.
Related
- Web → Doc mode: UI for the same surfaces (slash menu, image upload).
- Doc formats: Mermaid, math, callouts, cross-refs, embeds (with caps).
- Comments API: anchor a comment to a doc range.
- Workspaces API: pass
initialMarkdownon create to seed the body.