API · Endpoints

Comments

Inline comments anchored to a doc range, table cell, row, html element, surface, or whole workspace. The universal create endpoint accepts a discriminated target union; the legacy row-only endpoint is kept for older clients.

Generated from src/lib/api-paths/comments.ts. Body schemas come from CreateUniversalCommentSchema and CreateCommentSchema in the runtime.

get/api/workspaces/{slug}/comments

List comments on a workspace

Returns comments on this workspace, scoped + filtered by query params. Use `target_type` + `target_id` to scope to a row, cell, doc range, or surface; pass `mentioning=me` to surface only threads where the caller is mentioned; `status` defaults to `open` (pass `all` for resolved + open). Access-gated against the caller's workspace permissions, with the `commentsOnPublicShare` toggle on the workspace controlling whether unauthenticated viewers on public/unlisted workspaces can read.

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

Path parameters

NameTypeRequiredDescription
slugstringyes

Query parameters

NameTypeRequiredDescription
target_type"row" | "cell" | "doc_range" | "html_element" | "surface" | "workspace" | "file"no
target_idstringno
surfacestringno
status"open" | "resolved" | "all"no
mentioning"me"no
authorstringno
limitintegerno
offsetintegernullno

Responses

StatusBodyDescription
200objectComment list.
403ErrorCaller has no read access to this workspace.

post/api/workspaces/{slug}/comments

Create a comment (universal)

Create a comment anchored to a doc range, table cell, row, or whole workspace. The `target` discriminated union picks the anchor type.

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

Path parameters

NameTypeRequiredDescription
slugstringyes

Request body

FieldTypeRequiredDescription
targetobject | object | object | object | object | object | objectyes
bodystringyes
parentIdstringno
mentionsobject[]no
correlationIdstringno

Responses

StatusBodyDescription
200objectCreated.

get/api/workspaces/{slug}/rows/{id}/comments

List comments on a row (legacy)

Legacy row-scoped reader. Kept for back-compat with API clients that followed the older docs at `/docs/api/overview`. New clients should use `GET /api/workspaces/{slug}/comments?target_type=row&target_id=…` which carries the full v1 shape (reactions, resolvedAt, surfaceSlug, mentions in canonical form).

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

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Responses

StatusBodyDescription
200objectComment list, oldest-first.

post/api/workspaces/{slug}/rows/{id}/comments

Create a comment on a row (legacy)

Legacy row-only comment endpoint. New clients should use the universal endpoint above with `target: { kind: "row", rowId }`.

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

Path parameters

NameTypeRequiredDescription
slugstringyes
idstringyes

Request body

FieldTypeRequiredDescription
bodystringyes
parentIdstringno
mentionsstring[]no

Responses

StatusBodyDescription
200objectCreated.

get/api/comments/{id}

Get a single comment

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

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectComment detail.
404ErrorNot found.

delete/api/comments/{id}

Delete a comment

Author only.

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

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectDeleted.
403ErrorNot the author.

post/api/comments/{id}/resolve

Resolve a comment thread

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

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectResolved.

post/api/comments/{id}/unresolve

Re-open a resolved comment

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

Path parameters

NameTypeRequiredDescription
idstringyes

Responses

StatusBodyDescription
200objectRe-opened.

post/api/comments/{id}/reactions

Add an emoji reaction

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

Path parameters

NameTypeRequiredDescription
idstringyes

Request body

FieldTypeRequiredDescription
emojistringyes example: "👍"

Responses

StatusBodyDescription
200objectReacted.

delete/api/comments/{id}/reactions/{emoji}

Remove your reaction

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

Path parameters

NameTypeRequiredDescription
idstringyes
emojistringyes

Responses

StatusBodyDescription
200objectReaction removed.

Target shapes

Every create call carries a polymorphic target object whose type field picks the anchor. Six kinds, each with its own payload:

target.type = "row"

POST /api/workspaces/{slug}/comments
{
  "target": { "type": "row", "rowId": "cmp_row123" },
  "body": "Status looks off here."
}

target.type = "cell"

POST /api/workspaces/{slug}/comments
{
  "target": {
    "type": "cell",
    "rowId": "cmp_row123",
    "columnKey": "media_url"
  },
  "body": "Swap to the 16:9 crop?"
}

target.type = "doc_range"

POST /api/workspaces/{slug}/comments
{
  "target": {
    "type": "doc_range",
    "surfaceSlug": "notes",
    "anchor": {
      "from": 120,
      "to": 184,
      "text": "Ship a kill-switch flag before the partner-comms email."
    }
  },
  "body": "Who owns the kill-switch?"
}

The text snapshot lets the server detect anchor drift on read. If the doc body has changed by the time someone opens the thread, the response carries currentText vs expectedText so clients can re-anchor without a second round trip.

target.type = "html_element"

POST /api/workspaces/{slug}/comments
{
  "target": {
    "type": "html_element",
    "surfaceSlug": "mockup",
    "anchor": {
      "selector": "section.hero > h1",
      "text": "Workspaces built for both of you."
    }
  },
  "body": "Love this headline."
}

Pin a comment to a specific element inside an html-kind Surface. selector is the CSS path the resolver tries first; the optional 60-char text snippet is a fuzzy fallback for when the DOM is regenerated and the selector no longer resolves (the in-iframe capture script produces both on every click). Threads on the same surface share targetId (the surface id), so list responses scope replies by parent id.

target.type = "surface"

POST /api/workspaces/{slug}/comments
{
  "target": { "type": "surface", "surfaceSlug": "schedule" },
  "body": "Q4 cadence review thread starts here."
}

target.type = "workspace"

POST /api/workspaces/{slug}/comments
{
  "target": { "type": "workspace" },
  "body": "Workspace-wide retro thread."
}

target.type = "file"

Comments anchored on a single file in a files surface. The file uploader becomes the target author for the notification fan-out; any team mentions are appended to the audience.

POST /api/workspaces/{slug}/comments
{
  "target": { "type": "file", "fileId": "cmp_file123" },
  "body": "Replace the hero image, the contrast is too low."
}

Replying to a cell comment

The common failure mode: an agent receives a cell-anchored comment notification, tries to reply via the row-scoped endpoint POST /api/workspaces/{slug}/rows/{id}/comments, and the server 400s because that endpoint hard-codes target.type = "row". Use the universal endpoint with a matching target.type + the parent comment id:

POST /api/workspaces/{slug}/comments
{
  "target": {
    "type": "cell",
    "rowId": "cmp_row123",
    "columnKey": "media_url"
  },
  "parentId": "cmp_parent_cell_comment_id",
  "body": "Updated the crop, thanks for catching."
}

Single-depth threading: replying to a reply auto-flattens to the root. Replying to a resolved thread auto-reopens it and fires comment.unresolved on the workspace stream.

Row-scoped endpoint (deprecated)

The earlier POST /api/workspaces/{slug}/rows/{id}/comments endpoint still works for older clients but always resolves target.type = "row". Responses now carry an RFC 8594 Deprecation: true header + an RFC 8288 Link: </docs/api/comments>; rel="successor-version" pointing at this page. Use the universal endpoint above for any new agent code. Cross-target parent ids on the row-scoped endpoint return a 400 whose message names the parent’s actual target type and points back here.

MCP equivalents

Every endpoint on this page has a matching MCP tool:

  • add_comment, universal create with the same polymorphic target shape.
  • list_comments, query with the same filters as the GET endpoint.
  • get_comment_thread: fetch a parent + replies in one call.
  • reply_to_comment: shortcut when you already have a parent comment_idand don’t want to rebuild the target shape.
  • resolve_comment / unresolve_comment: close or reopen a thread.
  • react_to_comment: emoji reactions.

Frequently asked questions

How do I add a comment to a Dock row, cell, doc range, or html element via the API?
POST `/api/workspaces/:slug/comments` with `{ target: { type: 'row' | 'cell' | 'doc_range' | 'html_element' | 'surface' | 'workspace', ... }, body, mentions? }`. The `target` discriminated union picks the anchor; mentions trigger inbox notifications.
How do I list comments on a Dock workspace?
GET `/api/workspaces/:slug/comments`. Filter by `?target_type=row|cell|doc_range|html_element|surface|workspace&target_id=…` to scope to one anchor, `?surface=<slug>` for everything on one surface, `?status=open|resolved|all` (default open), `?mentioning=me` for thread your principal is mentioned in.
How do I @mention a Dock agent or person in a comment?
Include `@<name>` in the body AND pass `mentions: [{ kind: 'user' | 'agent', id, label }]` so the server fans out notifications without re-parsing. The body's `@<name>` is the human-readable rendering; `mentions[]` is the machine-readable mapping that drives inbox row + email (humans) or webhook (agents). The two arrays are filtered server-side: only mentions whose `@<label>` literal still appears in the body actually fire (so deleting `@Sarah` from the body before submit won't ping Sarah).
How do I resolve a Dock comment thread?
POST `/api/comments/:id/resolve`. Marks the thread resolved without deleting; resolved threads are excluded from the default list (pass `?status=resolved` or `?status=all` to include). POST `/api/comments/:id/unresolve` reverses it.
How do I add a reaction (emoji) to a Dock comment?
POST `/api/comments/:id/reactions` with `{ emoji }`. Unique by (comment, principal, emoji) so re-add is idempotent. DELETE the same path to remove. Supports any UTF-8 emoji string up to 16 chars.
How do I reply to a Dock comment via the API?
POST `/api/workspaces/:slug/comments` with the same target, plus `{ parentId: <root-comment-id> }`. Threading is one level deep; replies all attach to the root. Threads notify everyone who's already commented on the thread.
Can my AI agent leave a comment in a Dock workspace?
Yes, the same endpoints. The agent's principal id stamps `authorId + authorPrincipalType`. Useful for code-review-style agents that want to flag findings on specific rows or doc paragraphs for human review.
How does my Dock agent know it was @mentioned in a comment or doc?
Two webhook events cover both surfaces. Comments: subscribe to `comment.added`, read `mentions: [{kind, id, label}]`, filter on `mention.kind === 'agent' && mention.id === your-agent-id`. Doc bodies: subscribe to `doc.mention_added`, same `mentions` array shape, fires only on newly-added mentions (not on every save). The agent then reads the comment / doc context and replies via POST. Learn more →
Can outsiders see comments on a Dock public workspace?
Off by default. Set the workspace's `commentsOnPublicShare = true` (PATCH `/api/workspaces/:slug`) to let unauthenticated viewers on public/unlisted workspaces read comments. Default off because comments often contain context the owner doesn't want public.
How do I get notified when someone comments on something I authored in Dock?
Automatic. The Dock inbox routes target-author notifications: every comment on a row or doc you authored lands in your inbox without you needing to subscribe. For machine-readable, register a webhook on `comment.added` with author-filter logic in your handler. Learn more →
Updated