Developer docs
Agent workflows: review, revise, publish, manage
Agents work inside the approval workflow, never around it. On Team they review proposals: read the diff, comment, resolve threads and leave advisory verdicts. Wherever they may propose (one-click connections on Team, API keys too on Pro) they revise and withdraw their own proposals. On Pro they also publish proposals people approved, and create, rename, move and delete docs and folders. Every endpoint below has an MCP tool twin. To have every proposal reviewed against your checklist automatically, see AI reviewers.
Scopes per plan
A credential (API key or one-click MCP connection) can only do what its scopes allow and the organization's plan includes. Scopes are chosen when the key is created or on the consent screen.
| Scope | Lets an agent | API key | One-click connection |
|---|---|---|---|
docs:read | Read docs. Read published (approved) documents. | Free+ | Free+ |
docs:propose | Propose changes. Open proposals that your reviewers approve; nothing is published by the agent. | Pro | Team+ |
proposals:review | Review proposals. Read open proposals, comment on proposals and documents, reply, resolve threads and leave advisory verdicts. | Team+ | Team+ |
docs:write | Manage docs & folders. Create, rename, move and delete documents and folders. | Pro | Pro |
proposals:publish | Publish approved proposals. Publish a proposal once it meets the workspace approval rules. | Pro | Pro |
- Agent proposals: Team includes 25 agent proposals a month; Pro is unlimited. On Team only one-click connections (acting as their member) may propose.
docs:writeandproposals:publishchange the workspace without a human review step, so they are unchecked by default: a person must opt in.- A viewer's connection never holds
docs:propose,docs:writeorproposals:publish; viewers can still review (comment and leave advisory verdicts), as in the app. - Rate limits are unchanged: the same per-key budget, shared with the read endpoints and the MCP server.
Safety rules
- Agents act as a person. Writes run as the key's creator or the connected member, with their workspace rights, and are labeled via the key or app name in comments, activity and the audit log.
- Verdicts are advisory. An agent's approve / request changes / comment is shown to reviewers as an AI review. It never counts toward required approvals, never marks a proposal approved and never triggers auto-publish.
- Own proposals only. An agent revises or withdraws only proposals opened with the same credential (
403 not_agent_proposal); on anyone else's it comments. - Publish needs humans first. Only proposals whose status is
approvedpublish (409 not_approved), never with the admin override, never when stale (409 stale) and never past a workspace publish rule (409 publish_blocked). - No e-signatures. A workspace that requires re-authentication to publish answers
403 requires_e_signature: an agent cannot sign, a person publishes. - Approved records stay. Agents delete only documents with no published version (
409 published_document) and only empty folders (409 folder_not_empty), and must repeat the exact title inconfirm_title(400 confirm_title_mismatch). - Creating is not publishing. A document created with
content_markdownstarts empty; its text opens an Initial version proposal for review.
Review proposals (Team+)
All need the proposals:review scope.
proposalsTeam+ · proposals:reviewQuery parameters
| Name | Type | Description |
|---|---|---|
status | string | open (default: in review, changes requested, approved), all, or one status. Drafts are never listed. |
document_id | uuid | Only proposals on this document. |
limit | integer | 1–200, default 50. |
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/proposals?status=open&limit=20"{
"proposals": [{
"id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "title": "Clarify retry semantics", "status": "in_review",
"url": "https://versodocs.com/org/acme/eng/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d",
"document": { "id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "title": "Payments API", "workspace": "eng" },
"author": { "name": "Dana Ops", "email": "dana@acme.com" },
"opened_via_agent": false, "stale": false,
"opened_at": "2026-10-20T08:00:00.000Z", "updated_at": "2026-10-21T10:30:00.000Z"
}]
}stale: true means Current moved on since the proposal forked; it must be updated before it can publish.
proposals/{proposalId}Team+ · proposals:reviewThe summary fields plus description, base_version (the published version it forked from; null for a new document), content_markdown, diff { summary, unified }, reviewers[] (people and guests with their state), agent_reviews[] (advisory) and comments[] (with id, parent_id, quote, via, resolved).
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d"proposals/{proposalId}/commentsTeam+ · proposals:reviewJSON body
| Name | Type | Description |
|---|---|---|
body * | string | 1–10,000 characters. @mention members by email. |
quote | string | Exact text from one paragraph of the proposal: anchors an inline comment (422 quote_not_found otherwise). |
parent_id | uuid | Reply in this thread (inherits its anchor). Send quote or parent_id, not both; neither posts to the conversation. |
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "quote": "Retries up to 5 times", "body": "Does the client back off exponentially?" }' \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/comments"{ "comment_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "parent_id": null, "url": "…", "unresolved_mentions": [] }proposals/{proposalId}/comments/{commentId}Team+ · proposals:reviewBody { "resolved": true | false }. Any comment of the thread works; the whole thread flips.
curl -X PATCH -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "resolved": true }' \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/comments/7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d"{ "thread_root_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "resolved": true, "updated": 3 }proposals/{proposalId}/reviewsTeam+ · proposals:reviewJSON body
| Name | Type | Description |
|---|---|---|
verdict * | approve | request_changes | comment | The agent's verdict. |
body | string | Summary, up to 5,000 characters. Required for request_changes and comment. |
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "verdict": "request_changes", "body": "Section 3 contradicts the retry table." }' \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/reviews"{
"verdict": "changes_requested",
"advisory": true,
"counts_toward_approval": false,
"proposal_status": "in_review",
"url": "https://versodocs.com/org/acme/eng/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d"
}Advisory by design. The latest verdict per credential is shown to reviewers as an AI review and notifies the author. It is recorded apart from human reviews, so a required approval always needs a person. Closed and published proposals answer 409 proposal_closed.
Comment on documents (Team+)
Comment threads on published versions, the same threads people see on the document. Reading needs docs:read; commenting and resolving need proposals:review. Proposal review comments stay on the proposal endpoints above.
docs/{documentId}/commentsTeam+ · docs:readQuery parameters
| Name | Type | Description |
|---|---|---|
version | integer | uuid | A published version number or version id. Default: Current. |
include_resolved | boolean | true adds resolved threads. Default: open threads only. |
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/docs/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e/comments?include_resolved=true"{
"document": { "id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "title": "Payments API", "url": "…" },
"version": { "id": "…", "number": 14, "is_current": true },
"include_resolved": true,
"comments": [{
"id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "parent_id": null, "author": "Dana Ops",
"guest": false, "guest_name": null, "via": null,
"quote": "Retries up to 5 times", "body": "Still accurate?",
"resolved": false, "outdated": false,
"anchor_version": { "id": "…", "number": 13 }, "carried_forward": true,
"created_at": "2026-10-21T10:30:00.000Z"
}]
}Threads written on an older version and carried forward are included; anchor_version says which version each comment was written on. Share-link guests have guest: true and their guest_name.
docs/{documentId}/comments/{commentId}Team+ · proposals:reviewBody { "resolved": true | false }. Any comment of the thread works; the whole thread flips.
curl -X PATCH -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "resolved": true }' \
"https://versodocs.com/api/v1/orgs/acme/docs/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e/comments/7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d"{ "thread_root_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "resolved": true, "updated": 2 }Revise and withdraw (Team)
Open proposals with POST docs/{documentId}/proposals, as the whole document or as edits. Then, with docs:propose, revise or withdraw proposals this credential opened. On Team that is one-click connections (MCP update_proposal / close_proposal); on Pro API keys too.
proposals/{proposalId}Pro · docs:proposeJSON body (at least one)
| Name | Type | Description |
|---|---|---|
title | string | 1–200 characters. |
description | string | null | Context for reviewers. |
content_markdown | string | The whole new text. Send this or edits. |
edits | { find, replace }[] | 1–100 replacements applied to the proposal's current text (read it with GET proposals/{id}). Each find must match exactly once, else 422 edit_failed and nothing changes. |
curl -X PATCH -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "edits": [{ "find": "5 times", "replace": "5 times, with exponential backoff" }] }' \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d"{ "proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "status": "in_review", "approvals_dismissed": true, "url": "…" }A content change after approval dismisses the approvals (approvals_dismissed: true): reviewers must look again, exactly as after an edit in the app.
proposals/{proposalId}/closePro · docs:proposeWithdraw without publishing. Optional body { "reason": "…" } (up to 500 characters). Participants are notified.
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "reason": "Superseded by the v2 spec." }' \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/close"{ "proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "status": "closed" }Publish approved proposals (Pro)
proposals/{proposalId}/publishPro · proposals:publishMakes an approved proposal the new Current version, under the same gates as Publish in the app (author or workspace admin, edit access, up to date with Current, workspace publish rules), and stricter: humans must have approved it, and no override.
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/publish"{ "proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "status": "published", "version": 14, "document_id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "url": "…" }| Status | error | When |
|---|---|---|
| 409 | not_approved | Not approved yet: reviewers must approve first |
| 409 | stale | Current moved on: update the proposal, then get it re-approved |
| 409 | publish_blocked | A workspace publish rule or an outstanding changes requested (from a member or a guest reviewer) blocks it |
| 403 | requires_e_signature | The workspace requires an interactive e-signature; a person must publish |
Manage docs and folders (Pro)
All need docs:write. Folder targets are a folder_id / parent_id (uuid) or a folder_path / parent_path inside the workspace ("apis/v2"); null or "" is the workspace root. Moves stay within one workspace.
docsPro · docs:writeJSON body
| Name | Type | Description |
|---|---|---|
workspace * | string | Workspace slug, e.g. eng. |
title * | string | 1–200 characters. |
folder_id | folder_path | uuid | string | Where to create it. Default: workspace root. |
content_markdown | string | Optional first draft, opened as an Initial version proposal. Needs docs:propose too. |
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "workspace": "eng", "title": "Refunds API", "folder_path": "apis", "content_markdown": "# Refunds API\n…" }' \
"https://versodocs.com/api/v1/orgs/acme/docs"{
"document_id": "9a7c5e3b-2d4f-4a6c-8e1b-3f5d7a9c1e2b", "title": "Refunds API", "slug": "refunds-api", "workspace": "eng",
"url": "https://versodocs.com/org/acme/eng/apis/refunds-api",
"proposal": { "proposal_id": "…", "url": "…", "status": "in_review", … }
}At the plan's document cap: 402 plan_limit.
docs/{documentId}Pro · docs:writeRename (title; the URL slug follows) and/or move (folder_id | folder_path). Content and versions are untouched.
curl -X PATCH -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "folder_path": "apis/v2" }' \
"https://versodocs.com/api/v1/orgs/acme/docs/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e"{ "document_id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "title": "Payments API", "slug": "payments-api", "url": "…/eng/apis/v2/payments-api" }docs/{documentId}Pro · docs:writePermanent, with its drafts and proposals. Only a document with no published version; approved records answer 409 published_document and are deleted by a person in VersoDocs. confirm_title must repeat the exact title (case and spacing ignored).
curl -X DELETE -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/docs/9a7c5e3b-2d4f-4a6c-8e1b-3f5d7a9c1e2b?confirm_title=Refunds%20API"{ "document_id": "9a7c5e3b-2d4f-4a6c-8e1b-3f5d7a9c1e2b", "deleted": true }foldersPro · docs:writeBody { workspace, name, parent_id? | parent_path? }. The credential's person must be a member of the workspace with edit rights.
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
-d '{ "workspace": "eng", "name": "v2", "parent_path": "apis" }' \
"https://versodocs.com/api/v1/orgs/acme/folders"{ "folder_id": "4f8e2a1c-9b3d-4c7e-a6f0-1d2e3c4b5a69", "name": "v2", "slug": "v2", "workspace": "eng", "parent_id": "…" }folders/{folderId}Pro · docs:writeBody { name?, parent_id? | parent_path? }: rename and/or move.
{ "folder_id": "4f8e2a1c-9b3d-4c7e-a6f0-1d2e3c4b5a69", "name": "Version 2", "slug": "version-2" }folders/{folderId}Pro · docs:writeOnly an empty folder (409 folder_not_empty otherwise). confirm_title must repeat the folder name.
curl -X DELETE -H "Authorization: Bearer $VERSODOCS_TOKEN" \
"https://versodocs.com/api/v1/orgs/acme/folders/4f8e2a1c-9b3d-4c7e-a6f0-1d2e3c4b5a69?confirm_title=v2"Errors
On top of the common errors, as { error, message }:
| Status | error | Meaning |
|---|---|---|
| 400 | confirm_title_mismatch | confirm_title doesn't match the title / folder name |
| 402 | plan_limit | Document cap of the plan reached (create) |
| 403 | not_agent_proposal | Revise / close a proposal this credential didn't open |
| 403 | requires_e_signature | Publish in a workspace that requires an e-signature |
| 409 | proposal_closed | The proposal is closed or published |
| 409 | not_approved · stale · publish_blocked | Publish refused (see above) |
| 409 | published_document · folder_not_empty | Delete refused (see above) |
| 422 | edit_failed | An edits[i].find is missing or ambiguous; nothing was written |
| 422 | quote_not_found | A comment quote isn't in the proposal text (or the document version) |
Same actions over MCP
The MCP server exposes the same actions with the same inputs and rules. tools/list only shows a tool when the credential holds its scope, so an agent never sees write tools it wasn't granted.
| REST | MCP tool |
|---|---|
GET proposals | list_proposals |
GET proposals/{proposalId} | get_proposal |
POST proposals/{proposalId}/comments | comment_on_proposal |
PATCH proposals/{proposalId}/comments/{commentId} | resolve_comment |
GET docs/{documentId}/comments | list_document_comments |
POST docs/{documentId}/comments | comment_on_document |
PATCH docs/{documentId}/comments/{commentId} | resolve_document_comment |
POST proposals/{proposalId}/reviews | submit_review |
POST docs/{documentId}/proposals | propose_change |
POST docs/{documentId}/attachments | upload_attachment |
POST docs/{documentId}/attachments/{attachmentId}/confirm | REST only |
PATCH proposals/{proposalId} | update_proposal |
POST proposals/{proposalId}/close | close_proposal |
POST proposals/{proposalId}/publish | publish_proposal |
POST docs | create_doc |
PATCH docs/{documentId} | rename_doc, move_doc |
DELETE docs/{documentId} | delete_doc |
POST folders | create_folder |
PATCH folders/{folderId} | REST only |
DELETE folders/{folderId} | REST only |
Webhook events
Outgoing webhooks (Team+, configured per workspace) let your tooling react to agents and people alike. Each delivery is { event, created_at, data }, signed with x-versodocs-signature-v2.
| Event | When |
|---|---|
version.published | A document is published as a new version. |
version.acknowledged | A member confirms they have read the current version of a document. |
document.created | A new document is created in a workspace. |
document.updated | A document is renamed or moved to another folder. |
document.deleted | A document is permanently removed. |
document.commented | Someone (a member, an agent or a share-link guest) comments on a published version of a document or replies in a thread. |
proposal.opened | A reviewer is asked to review proposed changes to a document. |
proposal.updated | An open proposal’s title, description or content is revised through the API. |
proposal.commented | Someone (or an agent) comments on a proposal or replies in a thread. |
proposal.verdict_submitted | A reviewer approves a proposal, asks for changes, or leaves a comment. |
proposal.published | A proposal is published: its working copy becomes the new current version. |
proposal.closed | A proposal is closed by its author without publishing. |
review.requested | Someone asks an external reviewer bot (API key) to review a proposal. |
ai_review.completed | A hosted AI reviewer posted its (advisory) review on a proposal. |
ai_review.failed | A hosted AI reviewer gave up after retries (e.g. bad key, provider down). |
document.created, document.updated, document.deleted, document.commented, proposal.updated, proposal.commented and proposal.verdict_submitted carry actor_kind: "agent" when the action came through an API key or MCP connection, "human" (or absent) otherwise; document.commented uses "guest" for a comment through a share link that allows comments. Agent verdicts also carry advisory: true. review.requested, ai_review.completed and ai_review.failed come from AI reviewers.
{
"event": "proposal.verdict_submitted",
"created_at": "2026-10-21T10:31:02.000Z",
"data": {
"proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "document_id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "workspace_id": "…",
"reviewer_id": "…", "reviewer_name": "Dana Ops (via Claude Code)",
"verdict": "changes_requested", "proposal_status": "in_review",
"actor_kind": "agent", "advisory": true
}
}
docs/{documentId}/commentsTeam+ · proposals:reviewJSON body
body *stringquotestring422 quote_not_foundotherwise). Required unless replying.parent_iduuidquoteorparent_id, not both.versioninteger | uuidquote. Default: Current.