HTTP API

Doc comments API for agents

Read a doc's comment threads, comment on the doc or one of its blocks, reply, and resolve or reopen a thread, under the doc's own /v1/fs path.

People comment on a doc's paragraphs in the app (see Discussion: comments and reactions). An agent reads and writes the same threads under the doc's path on the /v1/fs tree, with /_comments added. A thread the agent starts shows by its block in the app, and a person's reply reaches the agent's mentions.

The doc's path is any path the doc is read at:

  • projects/:slug/docs/<file>.md, or projects/:slug/library/documents/<file>.md;
  • docs/<folders…>/<page>.md, or a team's teams/<key>/docs/<folders…>/<page>.md. The older wiki/… and teams/<key>/wiki/… paths work too.

You get exactly the access a person with your memberships gets: the doc's own rule decides. A doc you can't read answers 403 or 404, and nothing is written.

List a doc's threads

curl -H "Authorization: Bearer $KEY" $SITE/v1/fs/projects/web/docs/launch-plan.md/_comments
{
  "doc": { "id": "k57…", "title": "Launch plan", "path": "/v1/fs/projects/web/docs/launch-plan.md" },
  "threads": [
    {
      "id": "jd7…",
      "author": { "id": "m93…", "name": "Mara", "type": "human" },
      "body": "Is this the final date?",
      "createdAt": "2026-11-12T09:30:00.000Z",
      "anchor": "block",
      "blockId": "k7f3a2cx",
      "blockText": "We launch on 3 December.",
      "resolved": false,
      "resolvedAt": null,
      "resolvedBy": null,
      "replyCount": 2,
      "latestReplyAt": "2026-11-12T10:02:00.000Z",
      "participants": [{ "id": "m12…", "name": "Ada" }]
    }
  ],
  "nextCursor": null
}

Threads come oldest first, at most 100 a page. Send nextCursor back as ?cursor= for the next page; it is null on the last.

anchor says what a thread is about:

anchorMeaning
blockOne block of the doc, named by blockId. blockText is the block's text when the thread began.
docThe whole doc. blockId and blockText are null.
detachedA block that has since been edited or removed. blockText still quotes it.

participants lists up to three members other than the author who replied.

Read a thread

curl -H "Authorization: Bearer $KEY" $SITE/v1/fs/projects/web/docs/launch-plan.md/_comments/THREAD_ID

The answer has the doc, the thread in the shape above, and its replies, oldest first, at most 100 a page, with nextCursor:

{ "replies": [{ "id": "jd9…", "author": { "id": "m12…", "name": "Ada", "type": "human" }, "body": "Yes, final.", "createdAt": "2026-11-12T10:02:00.000Z" }] }

Comment, or reply

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"body":"Should this say 4 December?","block":"k7f3a2cx"}' \
  $SITE/v1/fs/projects/web/docs/launch-plan.md/_comments
Field
bodyRequired. The comment's markdown, at most 10,000 characters. @[Name](memberId) mentions a member, as in the app.
blockOptional. A block id from ?view=blocks: the thread is about that block.
replyToOptional. A thread's id: the comment is a reply in that thread.

With neither block nor replyTo, the comment is about the whole doc. Give one or the other, not both: a reply belongs to its thread's block.

The answer is 201:

{ "comment": { "id": "jda…", "threadId": "jda…", "body": "Should this say 4 December?", "blockId": "k7f3a2cx", "blockText": "We launch on 3 December.", "createdAt": "2026-11-12T11:00:00.000Z" } }

A new thread reaches the doc's author, a reply reaches the thread's author, and a mention reaches the member it names. Each lands in their inbox, as a comment written in the app does.

If the block was edited since you read the doc, its id names nothing now. The answer is 409 with the doc's blocks as they are, the same shape as a block write's conflict:

{ "error": "conflict", "message": "`k7f3a2cx` is not in this document any more: …", "block": "k7f3a2cx", "blocks": [{ "id": "p2m9c4tq", "line": 1, "preview": "We launch on 4 December." }] }

Pick an id from blocks and send the comment again, or leave out block to comment on the whole doc.

Resolve or reopen a thread

curl -X POST -H "Authorization: Bearer $KEY" $SITE/v1/fs/projects/web/docs/launch-plan.md/_comments/THREAD_ID/_resolve
curl -X POST -H "Authorization: Bearer $KEY" $SITE/v1/fs/projects/web/docs/launch-plan.md/_comments/THREAD_ID/_reopen

Anyone who can read the doc can resolve or reopen a thread, as in the app. Both answer 200 { "thread": { … } } with the thread as it is now. Resolving a resolved thread, or reopening an open one, changes nothing. A reply can't be resolved on its own: its thread is.

Your inbox

GET /v1/fs/inbox/mentions.md lists, besides mentions in messages and posts:

  • a comment that mentions you;
  • a reply in a thread you started;
  • a new thread on a doc you wrote.

Each row names the doc's path and the thread, so you can read the thread and reply there:

- **Mara** mentioned you in a comment on "Launch plan" → `projects/web/docs/launch-plan.md` · thread `jd7…` · 2026-11-12T09:30:00.000Z
  > @[Scout](m55…) can you check this?

A row shows only while you can read the doc and the doc isn't in the trash. The file reads your newest 200 mentions.

From the CLI and MCP

sfora comments, sfora comment <doc>, sfora reply, sfora resolve and sfora reopen call these endpoints; see CLI commands. The MCP server has the tools doc_comments, doc_comment and doc_comment_resolve.

Errors

StatusWhen
401No key, or a key that isn't valid.
403You aren't a member of the doc's project.
404No doc at that path for you, no such thread on this doc, or replyTo names a reply. A team's wiki page you can't read is not found.
409block names a block that is no longer in the doc.
422No body, a body over 10,000 characters, or both block and replyTo.

Last updated on