This guide is the human-readable companion to openapi.yaml. The examples use raw Authorization header values. Do not prefix JWTs or MCP tokens with Bearer.
GET /api/v1/private/projects/{project_id}/documents/{document_id}/overview returns version_count, endpoint_count, latest_version (the existing public version DTO or null), published_branch_ids, has_reviewed_draft, raw_size_bytes, and nullable raw_line_count. Counts and latest-version selection cover the whole document; published branch IDs and reviewed-draft evidence only include active branches. The response contains no document body or storage keys. Markdown statistics are stored at publication; older versions populate the derived metadata once on first overview access.
GET /api/v1/private/projects/{project_id}/documents/{document_id}/mcp-readiness returns nullable last_read_at. It searches successful published-content reads for the selected document across all history, using only the current user's active, unexpired tokens with a matching read scope. Archived parents or absence of matching evidence return null. Both endpoints require document read permission and keep the HTTP 200 envelope contract.
Summary polling reads only the target and its permission context. On startup and every minute, the worker marks a pending summary as failed if it has no matching durable job and has remained pending for at least five minutes. The grace period protects an older instance's in-flight generation; existing queued or leased jobs are preserved. Manual regeneration performs the same recovery check before reusing pending state.
| Surface | Base |
|---|---|
| Public REST | /api/v1/open |
| Private REST | /api/v1/private |
| OpenAPI document | /api/v1/open/docs/openapi.yaml |
| MCP JSON-RPC | /api/v1/open/mcp |
Public auth normally starts with login. Anonymous registration is disabled by default and must be explicitly enabled with VDOC_AUTH_ALLOW_REGISTRATION=true only for a trusted disposable or pilot environment. Private REST calls use the JWT returned in the Vdoc envelope. MCP calls use an MCP token created through the private REST API.
API_BASE="${API_BASE:-http://127.0.0.1:8080}"
PASSWORD="sample-password-change-me"
# This request requires VDOC_AUTH_ALLOW_REGISTRATION=true on the backend.
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')REST handlers return HTTP 200 for both success and application 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, NOT_FOUND, or INTERNAL. |
detail |
Optional result body. |
total |
Optional list count on list endpoints. |
Private project APIs use SuperAdmin plus project Reader, Writer, and Admin roles. Reader can query, Writer can upload draft and submit, and Admin can approve or reject.
Projects directly own typed Documents. document_type is 1 for OpenAPI and 2 for Markdown. relative_path is the document path identity stored by Vdoc; display names can change without creating a second persisted path/name identity.
List routes accept the PRD filters used by the workbench: document lists accept ?document_type=1|2, while draft and version lists accept ?branch_id={branch_id}. Draft detail/list responses include review_comment after request-changes, reject, or approve review actions.
| Category | Purpose |
|---|---|
| Open | Health, login, opt-in registration, OpenAPI YAML, MCP JSON-RPC, and capability-authenticated public document shares. |
| 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 stored raw/normalized or stable content. |
| Endpoints | Parsed endpoint list and detail from published versions. |
| Diffs | Semantic version comparison and summaries. |
| MCP Tokens | User MCP token lifecycle. |
| AI | Built-in Admin AI provider, prompt, AI summary, and page chat APIs. |
| Document Shares | Admin-managed public capability links and anonymous published-content access. |
The scriptable smoke path covers register/login, create project/document, upload draft, submit, approve, query endpoint, compare diff, create MCP token, MCP tools/list, and MCP tools/call.
TEAM_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/teams" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d '{"name":"Docs Team","description":"API docs smoke team"}')
TEAM_ID=$(printf '%s' "$TEAM_RESPONSE" | jq -r '.detail.id')
PROJECT_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-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 -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-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')
BRANCH_ID=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/branches" \
-H "Authorization: $JWT" | jq -r '.detail[] | select(.name=="dev") | .id')Document creation creates dev, test, and protected prod branches. Admins can add feature/* branches when needed.
Document lifecycle state is not patchable. PATCH /api/v1/private/projects/{project_id}/documents/{document_id} updates document metadata only; omit status (or send the unchanged active value for legacy clients). Archive a document only through POST /api/v1/private/projects/{project_id}/documents/{document_id}/archive. A rejected status change does not partially update the other fields.
Create a draft by posting OpenAPI 3.0 or 3.1 content as schema_content. Markdown documents use the same private REST draft routes and may post Markdown text as schema_content or content; MCP Markdown draft tools use markdown_content. content_kind accepts raw or normalized for OpenAPI content and raw or stable for Markdown content.
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.
DRAFT_ONE_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d "{\"branch_id\":\"$BRANCH_ID\",\"version_name\":\"1.0.0\",\"schema_content\":$SCHEMA_V1}")
DRAFT_ONE_ID=$(printf '%s' "$DRAFT_ONE_RESPONSE" | jq -r '.detail.id')
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ONE_ID/content/raw" -H "Authorization: $JWT"
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ONE_ID/content/normalized" -H "Authorization: $JWT"
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ONE_ID/submit" -X POST -H "Authorization: $JWT"
REVIEW_SNAPSHOT=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ONE_ID/content/raw" -H "Authorization: $JWT")
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_ONE_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/drafts/$DRAFT_ONE_ID/approve" \
-X POST -H "Authorization: $JWT" -H 'Content-Type: application/json' -d "$REVIEW_BODY")
VERSION_ONE_ID=$(printf '%s' "$VERSION_ONE_RESPONSE" | jq -r '.detail.id')Other review and promotion calls use:
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/promote
Review actions accept an optional JSON comment, trim it, persist it on the draft, return it as review_comment, and include it in review audit metadata without placing it in the published version. Unknown review fields are rejected.
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ONE_ID/content/raw" -H "Authorization: $JWT"
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ONE_ID/content/normalized" -H "Authorization: $JWT"
ENDPOINTS_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ONE_ID/endpoints?path=/pets" -H "Authorization: $JWT")
ENDPOINT_ID=$(printf '%s' "$ENDPOINTS_RESPONSE" | jq -r '.detail[0].id')
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/versions/$VERSION_ONE_ID/endpoints/$ENDPOINT_ID" -H "Authorization: $JWT"
DIFF_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/diffs" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d "{\"from_version_id\":\"$VERSION_ONE_ID\",\"to_version_id\":\"$VERSION_TWO_ID\"}")
DIFF_ID=$(printf '%s' "$DIFF_RESPONSE" | jq -r '.detail.id')
curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/diffs/$DIFF_ID/summary" -H "Authorization: $JWT"OpenAPI summaries use added_endpoints, removed_endpoints, modified_endpoints, and breaking_changes. Markdown summaries set document_format: 3 and use added_lines, removed_lines, modified_lines, and modified_blocks; endpoint counts remain zero for Markdown.
Endpoint details resolve local references and apply operation-level parameter overrides by name + in; duplicate identities at the same level are rejected. OpenAPI 3.1 Schema $ref siblings remain active alongside the referenced schema (represented with allOf), while 3.0 Reference Object siblings remain ignored. Used security scheme definitions are included in normalized_operation.securitySchemes; unused definitions are omitted. Changes to these definitions emit security_changed items at securitySchemes with the previous and current definitions, using the existing warning severity for security changes. This is available through both REST endpoint detail and MCP get_endpoint_detail; tool arguments are unchanged.
On upgraded backends, legacy published endpoint indexes are refreshed from hash-verified immutable source content when read. Cached OpenAPI version comparisons are recalculated and persisted on first access, retaining the version, endpoint, and diff IDs. The original published document is unchanged; object storage must remain available for this refresh.
Calling Compare requires an active Project and Document, and from_version_id must differ from to_version_id. Any two immutable published versions within that Document may be compared across branches, including archived Branches. Branch archive blocks new draft and AI work in that Branch but permits version comparisons. Project or Document archive blocks Compare requests; previously stored Diff records and summaries remain readable after their Project, Document, or Branch is archived.
MCP_TOKEN_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/mcp-tokens" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d '{"name":"docs-agent","scopes":[1,2]}')
MCP_TOKEN=$(printf '%s' "$MCP_TOKEN_RESPONSE" | jq -r '.detail.token')
MCP_TOKEN_ID=$(printf '%s' "$MCP_TOKEN_RESPONSE" | jq -r '.detail.id')
curl -sS "$API_BASE/api/v1/open/mcp" \
-H 'Content-Type: application/json' \
-H "Authorization: $MCP_TOKEN" \
-d '{"jsonrpc":"2.0","id":"tools-list","method":"tools/list"}'
curl -sS "$API_BASE/api/v1/open/mcp" \
-H 'Content-Type: application/json' \
-H "Authorization: $MCP_TOKEN" \
-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_ONE_ID\",\"endpoint_id\":\"$ENDPOINT_ID\"}}}"
curl -sS "$API_BASE/api/v1/private/mcp-usage?token_id=$MCP_TOKEN_ID&limit=20" \
-H "Authorization: $JWT"The token value in .detail.token is returned on creation and may be revealed again by the token owner with GET /api/v1/private/mcp-tokens/{token_id} while the token remains active. List, revoked, and expired token responses stay redacted. Storage uses token_hash for authentication and encrypted token_ciphertext for owner reveal; neither storage field is exposed by the API.
GET /api/v1/private/mcp-usage returns newest-first sanitized mcp.tool_call evidence for the authenticated user's tokens. token_id selects one owned token; SuperAdmins may specify another user's token, but an omitted token_id always means the current user's own tokens. The response is capped at 200 records and retains only the tool, adapter, result/reason, token ID, and canonical project/document/branch/draft/version/endpoint/diff IDs. Entity IDs are recorded only after a successful tool call, so a failed request cannot turn caller-supplied IDs into entity evidence. Successful published-content reads use evidence_kind=published_content_read. Raw MCP secrets, request arguments, schema/Markdown content, IP addresses, and User-Agent values are not returned.
Current MCP tools include list_projects, list_documents, list_document_branches, list_api_endpoints, list_api_versions, list_doc_versions, get_latest_schema, get_schema_version, get_endpoint_detail, compare_api_versions, get_change_summary, create_api_version_draft, update_api_version_draft, submit_api_version_draft, get_api_version_draft, get_latest_doc, get_doc_version, compare_doc_versions, create_doc_draft, update_doc_draft, submit_doc_draft, and get_doc_draft. Direct publish tools are intentionally not exposed in v0.1. list_api_versions is retained for API-document clients; Markdown agents should use the discoverable list_doc_versions tool with a doc:read scope.
MCP discovery in the current source version: list_document_branches(project_id, document_id) returns branch IDs/names/default/protection/status even before publication and requires the target document type's read scope. list_api_endpoints(project_id, document_id, version_id, method?, path?) returns endpoint summaries with IDs; method is case-insensitive and path matches the exact OpenAPI template. Use a returned ID with get_endpoint_detail. Older deployments may lack these tools; inspect tools/list before use.
SuperAdmins alone can read, update, and test the system OpenAI-compatible provider and system prompts at /api/v1/private/ai/*. Project Admins and SuperAdmins can read, update, and test project provider and prompt configuration under /api/v1/private/projects/{project_id}/ai/*; Reader and Writer roles are denied those configuration APIs even though they may use page summaries and Chat where document permissions allow. Provider responses expose api_key_set and api_key_last4 only. AI summary and chat outputs are AI-generated helper text and cannot approve, request changes, reject, publish, or modify drafts or versions.
Provider payloads accept tuning fields alongside name, base_url, model, api_mode, api_key, and enabled. temperature defaults to 0.2 and accepts 0 through 2. timeout_ms defaults to 30000 and accepts 1000 through 120000. max_output_tokens defaults to 1000 and accepts 1 through 32000. Project provider endpoints use the same request and response shape as the system provider.
The provider timeout controls the complete AI HTTP request. The default backend server.write_timeout is 180s to allow a 120-second provider call and request/response processing. Existing deployments with an explicit shorter write timeout should update it (or set VDOC_SERVER_WRITE_TIMEOUT=180s) before using longer provider timeouts; external proxy/client timeouts must also allow the request. Admin connects to the configured backend origin directly. The current MCP adapter defaults VDOC_MCP_TIMEOUT_MS to 180000 and accepts 1 through 180000. Upgrade older adapters before setting this value, and allow at least the same duration in the agent host's tool timeout when waiting for long AI-assisted draft submissions.
Token-limit truncation and other explicit incomplete provider responses fail the AI generation instead of saving a successful partial summary. Draft submission/publication retains its business result when an automatic AI summary fails; the AI panel reports the failed summary so it can be retried after changing the output limit. Context previews disclose truncation and carry version/branch identity; semantic-diff prompts include original change flags and old/new values.
Provider tests accept an optional request body. Omitting the body tests the saved effective configuration; for a project without an enabled override, this deliberately tests the enabled system-provider fallback. Prompt update bodies contain system_prompt, user_prompt_template, and enabled; the path is the single source of truth for prompt_key. Both prompt strings must be non-blank, every user_prompt_template must contain the literal {{context}}, and page_chat must additionally contain {{message}}.
curl -sS "$API_BASE/api/v1/private/ai/provider" \
-X PUT \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d '{"name":"docs-ai","base_url":"https://api.openai.example","model":"gpt-4.1-mini","api_mode":"chat_completions","api_key":"sk-change-me","enabled":true,"temperature":0.2,"timeout_ms":30000,"max_output_tokens":1000}'Submitting a draft atomically saves its submitted state, pending AI summary, and background job. Approving a draft atomically publishes the version and its summary job. Provider calls run after the transaction in a background worker, so submit and approve responses do not wait for AI. OpenAPI and Markdown draft submit and approve paths both follow this rule. The summary attempt is helper work, not part of the approval decision.
Manual ai-summary/regenerate also queues work and returns pending; repeating it while pending reuses that generation. Poll the matching GET endpoint every two seconds until a terminal status, and stop polling on navigation or read errors. The latest in-flight generation is visible as pending. Skipped and failed automatic summaries are saved as non-blocking skipped or failed records. Missing providers and disabled prompts are stored as skipped; provider call errors are stored as failed. A completion is rejected if its target, permissions, provider, or prompt changed while the provider call was running. These records are visible through the same ai-summary read endpoints and do not roll back submit or publish.
AI chat sends a bounded window of the current session history. Each session uses a persisted generation token so an older provider response cannot overwrite a newer request, including when requests are handled by different Vdoc instances. The chat-session collection supports GET with required document_id, context_type, and context_id query parameters so clients can recover all sessions for the current page context, newest-updated first.
Archiving is a read-only boundary for AI history. Project provider and prompt configuration remains readable to Project Admins and SuperAdmins after Project archive, while provider update/test and prompt update are blocked. Reader and Writer roles still cannot read that configuration. Existing Summary records, Chat sessions, and Chat messages remain readable after Project, Document, or target Branch archive. Manual regeneration, new Chat session creation, and new Chat messages require an active Project, Document, and target Branch. Manual Summary regeneration additionally requires Project Admin or SuperAdmin permission; readable project members may use Chat while the context is active. A provider completion that returns after the context or relevant configuration became stale is discarded.
This produces the following archive boundary across the private APIs:
| Resource after parent archive | Read/list | New work | Remaining lifecycle action |
|---|---|---|---|
| Draft | Allowed | Mutation blocked after Project, Document, or target Branch archive | None |
| Version, stored Diff | Allowed | Compare blocked after Project or Document archive; allowed after Branch archive | None |
| AI provider/prompts | Project Admin/SuperAdmin only | Update and provider test blocked | None |
| AI Summary/Chat | Allowed | Regenerate, create session, and send blocked | None |
| Document share | List allowed | Create and reveal blocked | Revoke allowed |
AI audit metadata includes token usage fields when the provider returns them: prompt_tokens, completion_tokens, and total_tokens. API keys, JWTs, MCP tokens, and Authorization headers are not stored in AI audit metadata. Provider test calls, manual summary regeneration, automatic summaries, and chat calls are audited with status and non-secret context.
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}/messages
Only Project Admins and SuperAdmins can manage public links. A branch must be active and already contain at least one published version before a link can be created. version_scope is 1 for the moving latest published version and 2 for all published versions on the branch. expiry_preset accepts 1_month, 3_months, 6_months, 1_year, or permanent; Admin selects 3_months by default. password is optional and, when present, must be 12–72 UTF-8 bytes with no leading or trailing Unicode whitespace.
After the parent Project, Document, or Branch is archived, Admins can still list retained share history and revoke an active link. Creating a link and revealing its capability are blocked. Anonymous public access remains unavailable whenever a parent is inactive and deliberately does not reveal which lifecycle check failed.
SHARE_RESPONSE=$(curl -sS "$API_BASE/api/v1/private/projects/$PROJECT_ID/documents/$DOCUMENT_ID/shares" \
-H 'Content-Type: application/json' \
-H "Authorization: $JWT" \
-d "{\"branch_id\":\"$BRANCH_ID\",\"version_scope\":2,\"expiry_preset\":\"1_month\",\"password\":\"sample share password\"}")
SHARE_ID=$(printf '%s' "$SHARE_RESPONSE" | jq -r '.detail.share.id')
SHARE_SECRET=$(printf '%s' "$SHARE_RESPONSE" | jq -r '.detail.secret')The complete browser URL is built as /share/{share_id}#{secret}. The fragment must be removed from the current history entry before any network request. Anonymous API calls send Authorization: VdocShare {secret}, omit account cookies/JWT, and receive Cache-Control: no-store, Referrer-Policy: no-referrer, and X-Robots-Tag: noindex protections.
Password-protected shares exchange the password for a 15-minute, share-bound proof:
POST /api/v1/open/document-shares/{share_id}/unlock
GET /api/v1/open/document-shares/{share_id}
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}/download
Send the proof as X-Vdoc-Share-Unlock. Invalid capabilities, passwords, proofs, revoked/expired links, and inactive parent resources all return the same public unavailable response. Downloads always pass through Vdoc authorization; object storage remains private. Markdown viewers must disable raw HTML, remote images, and unsafe links, while OpenAPI content is rendered only as escaped read-only text.
| Category | Methods | Path | Auth |
|---|---|---|---|
| Open | GET |
/api/v1/open/health |
Public |
| Open | GET |
/api/v1/open/auth/config |
Public |
| Open | POST |
/api/v1/open/auth/register |
Public |
| Open | POST |
/api/v1/open/auth/login |
Public |
| Open | GET |
/api/v1/open/docs/openapi.yaml |
Public |
| Open | POST |
/api/v1/open/mcp |
MCP token |
| Document Shares | GET |
/api/v1/open/document-shares/{share_id} |
Share capability |
| Document Shares | POST |
/api/v1/open/document-shares/{share_id}/unlock |
Share capability |
| Document Shares | GET |
/api/v1/open/document-shares/{share_id}/versions |
Share capability/proof |
| Document Shares | GET |
/api/v1/open/document-shares/{share_id}/versions/{version_id}/content |
Share capability/proof |
| Document Shares | GET |
/api/v1/open/document-shares/{share_id}/versions/{version_id}/download |
Share capability/proof |
| Identity | GET |
/api/v1/private/identity/me |
JWT |
| System Users | GET, POST |
/api/v1/private/system/users |
JWT |
| System Users | PATCH |
/api/v1/private/system/users/{user_id} |
JWT |
| System Users | GET |
/api/v1/private/system/users/{user_id}/mcp-tokens |
JWT |
| System Users | POST |
/api/v1/private/system/users/{user_id}/mcp-tokens/{token_id}/revoke |
JWT |
| Teams | GET, POST |
/api/v1/private/teams |
JWT; SuperAdmin |
| Teams | GET, PATCH |
/api/v1/private/teams/{team_id} |
JWT; SuperAdmin |
| Teams | POST |
/api/v1/private/teams/{team_id}/archive |
JWT; SuperAdmin |
| Projects | GET, POST |
/api/v1/private/projects |
JWT |
| Projects | GET, PATCH |
/api/v1/private/projects/{project_id} |
JWT |
| Projects | POST |
/api/v1/private/projects/{project_id}/archive |
JWT |
| Projects | GET, POST |
/api/v1/private/projects/{project_id}/members |
JWT |
| Projects | GET |
/api/v1/private/projects/{project_id}/member-candidates |
JWT; Project Admin or SuperAdmin |
| Projects | DELETE |
/api/v1/private/projects/{project_id}/members/{user_id} |
JWT |
| Projects | PATCH |
/api/v1/private/projects/{project_id}/members/{user_id}/role |
JWT |
| Documents | GET, POST |
/api/v1/private/projects/{project_id}/documents |
JWT |
| Documents | GET, PATCH |
/api/v1/private/projects/{project_id}/documents/{document_id} |
JWT |
| Documents | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/archive |
JWT |
| Document Shares | GET, POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/shares |
JWT (Project Admin/SuperAdmin) |
| Document Shares | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/shares/{share_id}/reveal |
JWT (Project Admin/SuperAdmin) |
| Document Shares | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/shares/{share_id}/revoke |
JWT (Project Admin/SuperAdmin) |
| Branches | GET, POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/branches |
JWT |
| Branches | GET, PATCH |
/api/v1/private/projects/{project_id}/documents/{document_id}/branches/{branch_id} |
JWT |
| Branches | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/branches/{branch_id}/archive |
JWT |
| Drafts | GET, POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts |
JWT |
| Drafts | GET, PATCH |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id} |
JWT |
| Drafts | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/content/{content_kind} |
JWT |
| Drafts | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/submit |
JWT |
| Drafts | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/approve |
JWT |
| Drafts | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/request-changes |
JWT |
| Drafts | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/reject |
JWT |
| Drafts | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/promote |
JWT |
| Versions | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions |
JWT |
| Versions | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id} |
JWT |
| Versions | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/content/{content_kind} |
JWT |
| Endpoints | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/endpoints |
JWT |
| Endpoints | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/endpoints/{endpoint_id} |
JWT |
| Diffs | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs |
JWT |
| Diffs | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs |
JWT |
| Diffs | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id} |
JWT |
| Diffs | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id}/summary |
JWT |
| Audit Logs | GET |
/api/v1/private/audit-logs |
JWT; super admin or scoped project admin |
| AI | GET, PUT |
/api/v1/private/ai/provider |
JWT; SuperAdmin |
| AI | POST |
/api/v1/private/ai/provider/test |
JWT; SuperAdmin |
| AI | GET, PUT |
/api/v1/private/projects/{project_id}/ai/provider |
JWT; Project Admin or SuperAdmin |
| AI | POST |
/api/v1/private/projects/{project_id}/ai/provider/test |
JWT; Project Admin or SuperAdmin |
| AI | GET |
/api/v1/private/ai/prompts |
JWT; SuperAdmin |
| AI | PUT |
/api/v1/private/ai/prompts/{prompt_key} |
JWT; SuperAdmin |
| AI | GET |
/api/v1/private/projects/{project_id}/ai/prompts |
JWT; Project Admin or SuperAdmin |
| AI | PUT |
/api/v1/private/projects/{project_id}/ai/prompts/{prompt_key} |
JWT; Project Admin or SuperAdmin |
| AI | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/ai-summary |
JWT |
| AI | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/drafts/{draft_id}/ai-summary/regenerate |
JWT |
| AI | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/ai-summary |
JWT |
| AI | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/versions/{version_id}/ai-summary/regenerate |
JWT |
| AI | GET |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id}/ai-summary |
JWT |
| AI | POST |
/api/v1/private/projects/{project_id}/documents/{document_id}/diffs/{diff_id}/ai-summary/regenerate |
JWT |
| AI | GET, POST |
/api/v1/private/projects/{project_id}/ai/chat-sessions |
JWT |
| AI | GET |
/api/v1/private/projects/{project_id}/ai/chat-sessions/{session_id} |
JWT |
| AI | POST |
/api/v1/private/projects/{project_id}/ai/chat-sessions/{session_id}/messages |
JWT |
| MCP Tokens | GET, POST |
/api/v1/private/mcp-tokens |
JWT |
| MCP Tokens | GET |
/api/v1/private/mcp-usage |
JWT; owner-scoped unless SuperAdmin supplies token_id |
| MCP Tokens | GET |
/api/v1/private/mcp-tokens/{token_id} |
JWT |
| MCP Tokens | POST |
/api/v1/private/mcp-tokens/{token_id}/revoke |
JWT |
- Version, endpoint, draft and Diff lists accept
page_size=1..200,offset=0..1000000, andsearch(up to 256 UTF-8 bytes). Version search matches the version name. Endpoint search matches method, path, summary, operation ID and tags. Responses includetotalandhas_more. Omittingpage_sizepreserves the legacy list contract. - Paged draft lists contain metadata only and omit
revision,review_revisionanddiff_preview; retrieve/drafts/{id}/content/rawfor a single review/edit snapshot. Paged Diff lists contain identity, version pair, status and timestamps only; retrieve/diffs/{id}for current summary and items. Search matches draft version names or either Diff version name. Draftbranch_idand Difffrom_version_id/to_version_idfilters remain available. Lists do not read schema objects or regenerate historical facts, and sort by creation time then ID descending. - Audit queries accept
limit(default 100, maximum 200),cursor, and RFC3339from(inclusive) /to(exclusive). The response includeshas_moreand, when available,next_cursor. Repeat the same filters with that cursor to continue beyond 200 records. Cursor pages omit a global total; project authorization is checked for every page. - Example:
GET /api/v1/private/projects/{project_id}/documents/{document_id}/versions?page_size=50&offset=50&search=v1. - Client cancellation propagates to database queries, object storage, and interactive AI requests. The API context deadline is 30 seconds for regular requests and 150 seconds for AI/MCP routes, bounded further by any earlier caller deadline. Errors retain HTTP 200 and use envelope statuses
CANCELLED(499) orDEADLINE_EXCEEDED(504). Database rollback/object cleanup has a separate bounded cleanup context. - Background summary jobs survive request cancellation and process restarts. One worker per instance claims jobs with a 180-second lease; each attempt is bounded to 150 seconds. Transient preparation/persistence errors retry up to three attempts with a five-second delay. Graceful shutdown releases durable work for retry; expired leases recover after crashes. Provider response failures are stored as
failedfor explicit retry. Targets, permissions, and generation tokens are rechecked before saving completion. No provider configuration storesskippedimmediately without enqueueing work. - Access logs include
route(the Gin route template),app_code, andapp_statusalongside the transport status, latency and trace ID. Business errors therefore remain distinguishable when transport HTTP status is 200. Request/response bodies and credential values are excluded.
get_latest_schema and get_latest_doc require an explicit branch_id. Missing branches and unsupported arguments return INVALID_ARGUMENT; a version_id cannot be silently ignored. To read an exact historical snapshot, use get_schema_version(project_id, document_id, version_id) or get_doc_version(project_id, document_id, version_id). Both return {version, content} and enforce the same document-type, project membership and read-scope boundaries as current content. Their audit evidence includes the actual version and branch.
get_api_version_draft preserves its metadata fields and adds content (content, hash, draft_id, content_kind, owner_type). Metadata, raw OpenAPI and revision are read from one snapshot. Pass that revision to updates; re-read and reconcile after a stale-revision error. These read-selection tools add no MCP publish capability. The parser/history upgrade described below requires migration 007.
-
Raw content, normalized/stable content and generated Diff snapshots must each fit the storage read limit (
server.max_body_size, default 10 MiB). Oversized objects returnINVALID_ARGUMENTbefore that object is written; previously staged objects are cleaned up and draft/version state is preserved on failure. A source below the request limit can still exceed the storage limit after JSON escaping or normalization. -
x-*extensions in the OpenAPI Paths Object remain literal source data and are not indexed as paths; real path names still require a leading/and at least one operation must be present. -
Local JSON Pointers support object keys, strict array indices, URI-fragment percent decoding and
~0/~1escapes. Local Path Item references are indexed before reading operations, including inherited path parameters and servers. Missing, remote, circular and conflicting Path Item references returnINVALID_ARGUMENTinstead of dropping operations. JSON and YAML numbers preserve exact decimal values through normalization, hashes and PostgreSQL JSONB reloads. -
Parsing checks cancellation and enforces cumulative limits: 16 MiB source, 200,000 visited nodes, 128 traversal levels, a conservative 16 MiB expansion estimate, and 16 MiB serialized operations. Numeric literals are limited to 4,096 characters and parsed exponents to ±1,000,000; canonical nonzero values must also fit PostgreSQL JSONB numeric (at most 131,072 digits before the decimal point and 16,383 after it). Out-of-range values fail with
INVALID_ARGUMENTduring draft creation/update, before publication. NUL characters in strings or object keys are rejected for the same JSONB storage constraint. These limits also count repeated references and YAML aliases. Normal create/update and review parsing use a request snapshot outside the shared Store lock; permissions, draft revisions and publication baseline are checked again before committing. -
OpenAPI 3.1 type arrays are compared as sets. Request narrowing and response widening are breaking; order changes are equivalent. Removing a required response guarantee is breaking. Enum additions are compatible in requests and breaking in responses; enum removals retain the existing conservative breaking classification in both directions. Adding an optional request body or media type does not make its internal required fields mandatory for previous callers.
-
Numeric/length/item/property bounds and
anyOfbranch sets are compared by request/response direction. Numeric bounds compare their effective inclusive/exclusive limits, with OpenAPI 3.0 boolean flags interpreted by that dialect; redundant limits are equivalent, including inside nested schemas.oneOfuses branch additions/removals only for disjoint type branches. Replacing branches, changed unsupported constraints or overlappingoneOfalternatives produceSchema compatibility requires manual review, with warning severity,is_breaking=falseandmust_handle=true. This is not an exhaustive JSON Schema implication solver; warning items must be reviewed rather than treated as proof of compatibility. -
OpenAPI
x-*extension payloads are literal data, including their$refkeys, and response-map extensions are not response statuses. Real named properties and headers beginning withx-still resolve references and participate in comparison. Original extension details remain available. -
Migration
007_parser_facts_and_history_pagesadds derived parser/hash markers, history indexes and a separatemust_handlecolumn. Historical endpoint indexes rebuild lazily from verified immutable raw content, preserve existing endpoint IDs and recover omitted referenced operations. Raw and original normalized objects/hashes remain unchanged. Draft previews and stored Diffs regenerate under parser version 8 when details are read. -
MCP authentication reads the current token by its indexed hash and locks its owner and token in one transaction. Last-use/expiry updates and success/failure audits commit together. Authentication does not load the global application snapshot; revoked tokens and inactive owners remain rejected. MCP tool names, arguments and scopes are unchanged.
-
Parameter
contentmedia schemas participate in comparison alongsideschema; changes between these representations or serialization options require manual review. Required property names are compared even without apropertiesdefinition. Existing media without a schema is treated as unconstrained. Canonicalization ignores genuine schema annotations while preserving identically named properties and all keys inside literal values. -
Private REST Diff items add
old_value_json/new_value_jsonstrings, and endpoint details addjson_previewstrings keyed by section (parameters,request_body,responses,security,servers,normalized_operation,schema_refs). These contain server-formatted JSON for exact rendering, including numeric versus string literals. Original structured fields remain available. Admin prefers the text fields so JavaScript numeric rounding cannot hide a change; clients on older backends fall back to their previous rendering. -
Successful MCP usage changes
last_used_atwithout changing the lifecycleupdated_atused for optimistic concurrency. Lifecycle writes retain the newer usage timestamp and still reject stale status changes. This allows concurrent token use and revocation without restoring stale token state. -
Diff property paths escape literal dots and backslashes in property names, keeping them distinct from nested paths.
-
Schema literal comparisons distinguish empty arrays from
null, including nestedconstvalues and enum/const intersections. Empty/absent equivalence is limited to optional endpoint collections. -
Effective server URLs/variables, response header contracts and request media encodings participate in Diff. Changes require manual review (
must_handle=true, warning severity); server/header descriptions and schema annotations are ignored. Header names are case-insensitive;Content-Typeentries in Header maps are ignored as required by OpenAPI. Missing/empty server lists use the default/server. -
Derived endpoint
normalized_operationincludes the sourceopenapiversion.nullableaffects standard validation in 3.0 only; 3.1 comparisons use type unions and ignore the legacy keyword without changing stored endpoint details or literal values. Local references insidedefaultresponses and encoding entries resolve with the same rules as other response and encoding names.