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.

ScopeLets an agentAPI keyOne-click connection
docs:readRead docs. Read published (approved) documents.Free+Free+
docs:proposePropose changes. Open proposals that your reviewers approve; nothing is published by the agent.ProTeam+
proposals:reviewReview proposals. Read open proposals, comment on proposals and documents, reply, resolve threads and leave advisory verdicts.Team+Team+
docs:writeManage docs & folders. Create, rename, move and delete documents and folders.ProPro
proposals:publishPublish approved proposals. Publish a proposal once it meets the workspace approval rules.ProPro
  • 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:write and proposals:publish change 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:write or proposals: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 approved publish (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 in confirm_title (400 confirm_title_mismatch).
  • Creating is not publishing. A document created with content_markdown starts empty; its text opens an Initial version proposal for review.

Review proposals (Team+)

All need the proposals:review scope.

GETproposalsTeam+ · proposals:review

Query parameters

NameTypeDescription
statusstringopen (default: in review, changes requested, approved), all, or one status. Drafts are never listed.
document_iduuidOnly proposals on this document.
limitinteger1–200, default 50.
bash
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
  "https://versodocs.com/api/v1/orgs/acme/proposals?status=open&limit=20"
200json
{
  "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.

GETproposals/{proposalId}Team+ · proposals:review

The 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).

bash
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
  "https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d"
POSTproposals/{proposalId}/commentsTeam+ · proposals:review

JSON body

NameTypeDescription
body *string1–10,000 characters. @mention members by email.
quotestringExact text from one paragraph of the proposal: anchors an inline comment (422 quote_not_found otherwise).
parent_iduuidReply in this thread (inherits its anchor). Send quote or parent_id, not both; neither posts to the conversation.
bash
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"
201json
{ "comment_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "parent_id": null, "url": "…", "unresolved_mentions": [] }
PATCHproposals/{proposalId}/comments/{commentId}Team+ · proposals:review

Body { "resolved": true | false }. Any comment of the thread works; the whole thread flips.

bash
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"
200json
{ "thread_root_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "resolved": true, "updated": 3 }
POSTproposals/{proposalId}/reviewsTeam+ · proposals:review

JSON body

NameTypeDescription
verdict *approve | request_changes | commentThe agent's verdict.
bodystringSummary, up to 5,000 characters. Required for request_changes and comment.
bash
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"
201json
{
  "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.

GETdocs/{documentId}/commentsTeam+ · docs:read

Query parameters

NameTypeDescription
versioninteger | uuidA published version number or version id. Default: Current.
include_resolvedbooleantrue adds resolved threads. Default: open threads only.
bash
curl -H "Authorization: Bearer $VERSODOCS_TOKEN" \
  "https://versodocs.com/api/v1/orgs/acme/docs/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e/comments?include_resolved=true"
200json
{
  "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.

POSTdocs/{documentId}/commentsTeam+ · proposals:review

JSON body

NameTypeDescription
body *string1–10,000 characters. @mention members by email.
quotestringExact text from one paragraph of the version: starts an inline thread (422 quote_not_found otherwise). Required unless replying.
parent_iduuidReply in this thread (joins its version and anchor). Send quote or parent_id, not both.
versioninteger | uuidThe published version to comment on, with quote. Default: Current.
bash
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" -H "Content-Type: application/json" \
  -d '{ "quote": "Retries up to 5 times", "body": "Is this still true after the gateway change?" }' \
  "https://versodocs.com/api/v1/orgs/acme/docs/0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e/comments"
201json
{ "comment_id": "7e2d9c4b-1a3f-4b6e-8d0c-5f9a2e7b3c1d", "parent_id": null, "version": { "id": "…", "number": 14 }, "url": "…", "unresolved_mentions": [] }
PATCHdocs/{documentId}/comments/{commentId}Team+ · proposals:review

Body { "resolved": true | false }. Any comment of the thread works; the whole thread flips.

bash
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"
200json
{ "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.

PATCHproposals/{proposalId}Pro · docs:propose

JSON body (at least one)

NameTypeDescription
titlestring1–200 characters.
descriptionstring | nullContext for reviewers.
content_markdownstringThe 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.
bash
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"
200json
{ "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.

POSTproposals/{proposalId}/closePro · docs:propose

Withdraw without publishing. Optional body { "reason": "…" } (up to 500 characters). Participants are notified.

bash
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"
200json
{ "proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "status": "closed" }

Publish approved proposals (Pro)

POSTproposals/{proposalId}/publishPro · proposals:publish

Makes 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.

bash
curl -X POST -H "Authorization: Bearer $VERSODOCS_TOKEN" \
  "https://versodocs.com/api/v1/orgs/acme/proposals/c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d/publish"
200json
{ "proposal_id": "c3a1f0e2-7b4d-4e8a-9c1d-2f3e4a5b6c7d", "status": "published", "version": 14, "document_id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "url": "…" }
StatuserrorWhen
409not_approvedNot approved yet: reviewers must approve first
409staleCurrent moved on: update the proposal, then get it re-approved
409publish_blockedA workspace publish rule or an outstanding changes requested (from a member or a guest reviewer) blocks it
403requires_e_signatureThe 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.

POSTdocsPro · docs:write

JSON body

NameTypeDescription
workspace *stringWorkspace slug, e.g. eng.
title *string1–200 characters.
folder_id | folder_pathuuid | stringWhere to create it. Default: workspace root.
content_markdownstringOptional first draft, opened as an Initial version proposal. Needs docs:propose too.
bash
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"
201json
{
  "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.

PATCHdocs/{documentId}Pro · docs:write

Rename (title; the URL slug follows) and/or move (folder_id | folder_path). Content and versions are untouched.

bash
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"
200json
{ "document_id": "0b6c2d8e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "title": "Payments API", "slug": "payments-api", "url": "…/eng/apis/v2/payments-api" }
DELETEdocs/{documentId}Pro · docs:write

Permanent, 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).

bash
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"
200json
{ "document_id": "9a7c5e3b-2d4f-4a6c-8e1b-3f5d7a9c1e2b", "deleted": true }
POSTfoldersPro · docs:write

Body { workspace, name, parent_id? | parent_path? }. The credential's person must be a member of the workspace with edit rights.

bash
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"
201json
{ "folder_id": "4f8e2a1c-9b3d-4c7e-a6f0-1d2e3c4b5a69", "name": "v2", "slug": "v2", "workspace": "eng", "parent_id": "…" }
PATCHfolders/{folderId}Pro · docs:write

Body { name?, parent_id? | parent_path? }: rename and/or move.

200json
{ "folder_id": "4f8e2a1c-9b3d-4c7e-a6f0-1d2e3c4b5a69", "name": "Version 2", "slug": "version-2" }
DELETEfolders/{folderId}Pro · docs:write

Only an empty folder (409 folder_not_empty otherwise). confirm_title must repeat the folder name.

bash
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 }:

StatuserrorMeaning
400confirm_title_mismatchconfirm_title doesn't match the title / folder name
402plan_limitDocument cap of the plan reached (create)
403not_agent_proposalRevise / close a proposal this credential didn't open
403requires_e_signaturePublish in a workspace that requires an e-signature
409proposal_closedThe proposal is closed or published
409not_approved · stale · publish_blockedPublish refused (see above)
409published_document · folder_not_emptyDelete refused (see above)
422edit_failedAn edits[i].find is missing or ambiguous; nothing was written
422quote_not_foundA 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.

RESTMCP tool
GET proposalslist_proposals
GET proposals/{proposalId}get_proposal
POST proposals/{proposalId}/commentscomment_on_proposal
PATCH proposals/{proposalId}/comments/{commentId}resolve_comment
GET docs/{documentId}/commentslist_document_comments
POST docs/{documentId}/commentscomment_on_document
PATCH docs/{documentId}/comments/{commentId}resolve_document_comment
POST proposals/{proposalId}/reviewssubmit_review
POST docs/{documentId}/proposalspropose_change
POST docs/{documentId}/attachmentsupload_attachment
POST docs/{documentId}/attachments/{attachmentId}/confirmREST only
PATCH proposals/{proposalId}update_proposal
POST proposals/{proposalId}/closeclose_proposal
POST proposals/{proposalId}/publishpublish_proposal
POST docscreate_doc
PATCH docs/{documentId}rename_doc, move_doc
DELETE docs/{documentId}delete_doc
POST folderscreate_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.

EventWhen
version.publishedA document is published as a new version.
version.acknowledgedA member confirms they have read the current version of a document.
document.createdA new document is created in a workspace.
document.updatedA document is renamed or moved to another folder.
document.deletedA document is permanently removed.
document.commentedSomeone (a member, an agent or a share-link guest) comments on a published version of a document or replies in a thread.
proposal.openedA reviewer is asked to review proposed changes to a document.
proposal.updatedAn open proposal’s title, description or content is revised through the API.
proposal.commentedSomeone (or an agent) comments on a proposal or replies in a thread.
proposal.verdict_submittedA reviewer approves a proposal, asks for changes, or leaves a comment.
proposal.publishedA proposal is published: its working copy becomes the new current version.
proposal.closedA proposal is closed by its author without publishing.
review.requestedSomeone asks an external reviewer bot (API key) to review a proposal.
ai_review.completedA hosted AI reviewer posted its (advisory) review on a proposal.
ai_review.failedA 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.

proposal.verdict_submittedjson
{
  "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
  }
}