API Reference
This page is for scripts, Admin debugging, and Agent integration. It is not the full OpenAPI file. The backend serves the machine-readable definition at /api/v1/open/docs/openapi.yaml.
Base Paths
| Surface | Path | Auth |
|---|---|---|
| Public REST | /api/v1/open/* | Route-specific; most public routes are open. |
| Private REST | /api/v1/private/* | Raw JWT in Authorization. |
| OpenAPI document | /api/v1/open/docs/openapi.yaml | None. |
| MCP JSON-RPC | /api/v1/open/mcp | Raw MCP Token. |
The default backend origin for local full Compose is http://127.0.0.1:8080. Use your backend domain in deployed environments.
API_BASE="${API_BASE:-http://127.0.0.1:8080}"
curl "$API_BASE/api/v1/open/health"
curl "$API_BASE/api/v1/open/docs/openapi.yaml"Authentication Rule
Public auth normally starts with login. Anonymous registration is disabled by default and should be enabled with VDOC_AUTH_ALLOW_REGISTRATION=true only in a trusted disposable or pilot environment. Private REST uses the JWT returned by login or registration. MCP uses an MCP Token created in Admin or private REST.
Key rule: Authorization contains the raw JWT or MCP Token. Do not add Bearer. Never paste full header values into docs, logs, screenshots, or commits. Shell environment variables are not package CLI arguments, so never put credentials in package or process args. Run set +x to disable xtrace, then pass the header to curl through stdin config so the raw value does not enter curl process arguments.
Response Envelope
REST handlers may return HTTP 200 for both success and business errors. The semantic result is inside the JSON body.
| Field | Meaning |
|---|---|
code | Semantic status code such as 200, 400, 401, 403, 404, 409, or 500. |
status | Semantic status text such as OK, INVALID_ARGUMENT, UNAUTHENTICATED, PERMISSION_DENIED, or INTERNAL. |
message | Caller-facing message. |
detail | Success result or error detail. |
total | Optional total for list endpoints. |
trace_id | Trace identifier for debugging. |
timestamp | Response time. |
Register or Log In
This example is only for local smoke tests. Do not use a real user password.
PASSWORD="sample-password-change-me"
# The backend must explicitly set VDOC_AUTH_ALLOW_REGISTRATION=true.
REGISTER_RESPONSE=$(curl -sS "$API_BASE/api/v1/open/auth/register" \
-H 'Content-Type: application/json' \
-d '{"email":"docs-admin@example.test","name":"Docs Admin","password":"sample-password-change-me"}')
ADMIN_USER_ID=$(printf '%s' "$REGISTER_RESPONSE" | jq -r '.detail.user.id')
JWT=$(printf '%s' "$REGISTER_RESPONSE" | jq -r '.detail.token')
set +x
curl_with_jwt() {
printf 'header = "Authorization: %s"\n' "$JWT" |
curl --config - "$@"
}Verify private identity:
curl_with_jwt -sS "$API_BASE/api/v1/private/identity/me"Roles and Document Types
- SuperAdmin can manage and approve at the system level.
- Project Reader can query.
- Project Writer can upload Drafts and submit them.
- Project Admin can approve, request changes, or reject.
document_type=1means OpenAPI.document_type=2means Markdown.relative_pathis stable Document identity. Display name changes should not change it.
Create Team, Project, and Document
TEAM_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/teams" \
-H 'Content-Type: application/json' \
-d '{"name":"Docs Team","description":"API docs smoke team"}')
TEAM_ID=$(printf '%s' "$TEAM_RESPONSE" | jq -r '.detail.id')
PROJECT_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects" \
-H 'Content-Type: application/json' \
-d "{\"team_id\":\"$TEAM_ID\",\"name\":\"Docs Project\",\"description\":\"API docs smoke project\",\"admin_user_id\":\"$ADMIN_USER_ID\"}")
PROJECT_ID=$(printf '%s' "$PROJECT_RESPONSE" | jq -r '.detail.id')
DOCUMENT_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents" \
-H 'Content-Type: application/json' \
-d '{"name":"petstore","document_type":1,"relative_path":"apis/petstore.yaml","description":"Docs sample document"}')
DOCUMENT_ID=$(printf '%s' "$DOCUMENT_RESPONSE" | jq -r '.detail.id')After Document creation, Vdoc creates dev, test, and protected prod branches. Get the dev branch:
BRANCH_ID=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/branches" |
jq -r '.detail[] | select(.name=="dev") | .id')Draft, Review, and Version
OpenAPI Drafts submit OpenAPI 3.0 or 3.1 content as schema_content. Markdown Drafts use the same private REST draft routes and may submit Markdown text as schema_content or content. MCP Markdown draft tools use markdown_content. content_kind accepts raw or normalized for OpenAPI, and raw or stable for Markdown.
Draft responses include an opaque revision. Every REST draft PATCH and MCP update_api_version_draft / update_doc_draft call requires expected_revision from the draft snapshot the edits were based on. A missing revision returns INVALID_ARGUMENT; a stale revision returns FAILED_PRECONDITION without changing the draft. Reload and reconcile local edits before retrying. REST draft content responses include detail.draft alongside content and hash, all from one snapshot; editors must use this nested draft's metadata and revision. Deploy the backend and Admin together, and update MCP clients to pass the new required field.
Submitted draft snapshots also include review_revision. Approval, request-changes, and rejection require expected_review_revision from the exact detail.draft returned with the content and diff the reviewer inspected. The review revision binds content, submission round, and the current branch latest used by the preview. Missing values return INVALID_ARGUMENT; changed content, resubmission (even with identical content), or a newer branch publication return FAILED_PRECONDITION. Reload the content and diff, preserve the review note, and make a new review decision. The server also rechecks the draft and branch baseline within the publish transaction. Published drafts retain their historical review baseline. Backend and Admin must be upgraded together.
Before running the OpenAPI example, set SCHEMA_V1 to a valid JSON string, for example with jq -Rs . < openapi.yaml.
DRAFT_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts" \
-H 'Content-Type: application/json' \
-d "{\"branch_id\":\"$BRANCH_ID\",\"version_name\":\"1.0.0\",\"schema_content\":$SCHEMA_V1}")
DRAFT_ID=$(printf '%s' "$DRAFT_RESPONSE" | jq -r '.detail.id')
curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ID/submit" \
-X POST
REVIEW_SNAPSHOT=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ID/content/raw")
printf '%s' "$REVIEW_SNAPSHOT" | jq '{content: .detail.content, diff: .detail.draft.diff_preview}'
# Inspect the content and diff before approving this snapshot.
REVIEW_BODY=$(printf '%s' "$REVIEW_SNAPSHOT" | jq -c '{expected_review_revision: .detail.draft.review_revision}')
VERSION_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ID/approve" \
-X POST -H 'Content-Type: application/json' -d "$REVIEW_BODY")
VERSION_ID=$(printf '%s' "$VERSION_RESPONSE" | jq -r '.detail.id')Other review actions:
POST /api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/request-changes
POST /api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/reject
POST /api/v1/private/projects/{project_id}/documents/{document_id}/drafts/promoteQuery Version, Endpoint, and Diff
curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ID/content/raw"
ENDPOINTS_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ID/endpoints?path=/pets")
ENDPOINT_ID=$(printf '%s' "$ENDPOINTS_RESPONSE" | jq -r '.detail[0].id')
curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ID/endpoints/$ENDPOINT_ID"Compare two published Versions:
DIFF_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/diffs" \
-H 'Content-Type: application/json' \
-d "{\"from_version_id\":\"$VERSION_ONE_ID\",\"to_version_id\":\"$VERSION_TWO_ID\"}")
DIFF_ID=$(printf '%s' "$DIFF_RESPONSE" | jq -r '.detail.id')
curl_with_jwt -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/diffs/$DIFF_ID/summary"MCP Token and MCP JSON-RPC
Create an MCP Token. .detail.token is copyable, and its owner can reveal an active token again through the detail endpoint. List, revoked, and expired responses are masked.
MCP_TOKEN_RESPONSE=$(curl_with_jwt -sS "$API_BASE/api/v1/private/mcp-tokens" \
-H 'Content-Type: application/json' \
-d '{"name":"docs-agent","scopes":[1,2]}')
MCP_TOKEN=$(printf '%s' "$MCP_TOKEN_RESPONSE" | jq -r '.detail.token')
curl_with_mcp_token() {
printf 'header = "Authorization: %s"\n' "$MCP_TOKEN" |
curl --config - "$@"
}List MCP tools:
curl_with_mcp_token -sS "$API_BASE/api/v1/open/mcp" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"tools-list","method":"tools/list"}'Example read tool call:
curl_with_mcp_token -sS "$API_BASE/api/v1/open/mcp" \
-H 'Content-Type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"id\":\"endpoint-detail\",\"method\":\"tools/call\",\"params\":{\"name\":\"get_endpoint_detail\",\"arguments\":{\"project_id\":\"$PROJECT_ID\",\"document_id\":\"$DOCUMENT_ID\",\"version_id\":\"$VERSION_ID\",\"endpoint_id\":\"$ENDPOINT_ID\"}}}"v0.2 does not expose direct publish tools through MCP. Agents can create, update, view, and submit Drafts, but publishing still requires Admin or SuperAdmin approval.
Admin AI Routes
Admin AI uses private JWT APIs. Provider and prompt configuration is separate from the external MCP/Skill Agent, and AI output cannot replace machine Diff or human review.
Only SuperAdmins may read, update, or test system Provider/Prompt configuration. Only the corresponding Project Admin or a SuperAdmin may read, update, or test project Provider/Prompt configuration. Readers and Writers cannot read this configuration, but may still use summaries and page Chat where document permissions allow. Omitting the body from a project provider test tests the effective configuration; without an enabled project override, it tests the system fallback provider.
GET /api/v1/private/ai/provider
PUT /api/v1/private/ai/provider
POST /api/v1/private/ai/provider/test
GET /api/v1/private/projects/{project_id}/ai/provider
PUT /api/v1/private/projects/{project_id}/ai/provider
POST /api/v1/private/projects/{project_id}/ai/provider/test
GET /api/v1/private/ai/prompts
PUT /api/v1/private/ai/prompts/{prompt_key}
GET /api/v1/private/projects/{project_id}/ai/prompts
PUT /api/v1/private/projects/{project_id}/ai/prompts/{prompt_key}
GET /api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/ai-summary
POST /api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/ai-summary/regenerate
GET /api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/ai-summary
POST /api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/ai-summary/regenerate
GET /api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id}/ai-summary
POST /api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id}/ai-summary/regenerate
GET /api/v1/private/projects/{project_id}/ai/chat-sessions?document_id={document_id}&context_type={draft|version|diff}&context_id={context_id}
POST /api/v1/private/projects/{project_id}/ai/chat-sessions
GET /api/v1/private/projects/{project_id}/ai/chat-sessions/{session_id}
POST /api/v1/private/projects/{project_id}/ai/chat-sessions/{session_id}/messagesProvider payloads contain name, base_url, model, api_mode, api_key, enabled, temperature, timeout_ms, and max_output_tokens. Read responses expose only api_key_set and api_key_last4. Prompt update bodies contain only system_prompt, user_prompt_template, and enabled; every user template requires , while page_chat also requires . System and project providers both have test routes. Draft, Version, and Diff all support summary reads and manual regeneration, while chat sessions stay bound to the current page resource context and can be recovered through the collection GET. After Project archival, only Project Admins and SuperAdmins retain read-only access to historical Provider/Prompt configuration. Historical summaries and Chat remain readable, but regeneration, session creation, and message sending are blocked.
Public Document Shares
Project Admins and SuperAdmins can create, reveal, and revoke public links for Branches that already have published versions from the Documents page. The secret exists only in the URL fragment; the anonymous page removes it from the address and current history entry before any network request and never attaches account cookies or JWTs.
Admin defaults to a three-month expiry and also offers one month, six months, one year, or permanent. An optional password must contain 12–72 UTF-8 bytes with no leading or trailing Unicode whitespace; multi-byte characters count by their encoded byte length.
GET, POST /api/v1/private/projects/{project_id}/documents/{document_id}/shares
POST /api/v1/private/projects/{project_id}/documents/{document_id}/shares/{share_id}/reveal
POST /api/v1/private/projects/{project_id}/documents/{document_id}/shares/{share_id}/revoke
GET /api/v1/open/document-shares/{share_id}
POST /api/v1/open/document-shares/{share_id}/unlock
GET /api/v1/open/document-shares/{share_id}/versions
GET /api/v1/open/document-shares/{share_id}/versions/{version_id}/content
GET /api/v1/open/document-shares/{share_id}/versions/{version_id}/downloadPublic requests use Authorization: VdocShare {secret}. Password-protected links exchange the password at /unlock for a 15-minute proof bound to that share and send it as X-Vdoc-Share-Unlock. Revoked or expired links, invalid proofs, and inactive parent resources all return the same unavailable result. Markdown disables raw HTML, remote images, and unsafe links; OpenAPI is escaped read-only text; every download passes through backend authorization.
Route Categories
| Category | Purpose |
|---|---|
| Open | Health, login, opt-in register, OpenAPI YAML, and MCP JSON-RPC. |
| Identity | Current JWT user identity. |
| System Users | SuperAdmin user lifecycle and user MCP token oversight. |
| Teams | Team lifecycle. |
| Projects | Project lifecycle and membership. |
| Documents | Project document lifecycle. |
| Branches | Document branch lifecycle. |
| Drafts | Draft creation, update, submission, review, and promotion. |
| Versions | Published document versions and raw, normalized, or stable content. |
| Endpoints | Endpoint list and detail parsed from published versions. |
| Diffs | Semantic version comparison and summaries. |
| AI | Provider, prompts, Draft/Version/Diff AI summaries, and page chat. |
| MCP Tokens | User MCP token lifecycle. |
| Document Shares | Admin-managed public links and anonymous published-content access. |
Verification
curl $API_BASE/api/v1/open/healthsucceeds.- Register or login returns an envelope, and
detail.tokenworks withGET /api/v1/private/identity/me. - After create Draft, submit, and approve, Version content can be read.
POST /api/v1/open/mcptools/listreturns tool schemas.- Real JWTs, MCP Tokens, or
Authorizationheader values do not appear in examples, logs, or Git history.
Common Mistakes
- Checking only HTTP status is misleading; inspect envelope
codeandstatus. - Do not add
BearertoAuthorization. - Do not put MCP Tokens in CLI args. Put them in Agent MCP config
env. - If an Agent says it published a Version, confirm it only submitted a Draft. v0.2 does not support MCP direct publish.