HTTP API and WebSocket Reference
Note: Examples use
http://localhost:8000. Host, port, cookie security, and default AI settings are configurable. See Configuration Reference for deployment settings.
Authentication
Protected HTTP routes accept either Authorization: Bearer <token> or a valid docsfy_session cookie. The WebSocket endpoint accepts either the same session cookie or ?token=<token> in the connection URL.
| Name | Type | Default | Description |
|---|---|---|---|
Authorization |
HTTP header | none | Bearer <ADMIN_KEY> or Bearer <user_api_key>. Accepted on protected /api/* routes and /docs/* file routes. |
docsfy_session |
cookie | none | Opaque session token created by POST /api/auth/login. Cookie attributes: HttpOnly, SameSite=Strict, Max-Age=28800; Secure follows the server secure_cookies setting. |
token |
query string | none | Raw ADMIN_KEY or user API key for ws://.../api/ws. |
curl -H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/projects
const ws = new WebSocket("ws://localhost:8000/api/ws?token=<USER_API_KEY>");
Authenticates protected HTTP requests and the WebSocket handshake.
Access rules:
| Role or identity | Read project data | Generate, abort, delete | Admin endpoints | Key rotation |
|---|---|---|---|---|
admin DB user |
Yes | Yes | Yes | Yes |
Bootstrap ADMIN_KEY identity |
Yes | Yes | Yes | No |
user |
Yes | Yes, for owned variants only | No | Yes |
viewer |
Yes | No | No | Yes |
Warning: Project and variant read routes return
404instead of403when a resource exists but is not accessible to the caller.Warning: Unauthenticated
/docs/*requests withAccept: text/htmlreceive302 /login. Other unauthenticated/docs/*requests receive401 {"detail":"Unauthorized"}.
Common response objects
ProjectVariant object
Used by project listing, project lookup, generation lookup, and WebSocket sync payloads.
| Name | Type | Description |
|---|---|---|
name |
string | Project name derived from repo_url or the basename of repo_path. |
branch |
string | Git branch for this variant. |
ai_provider |
string | AI provider used for generation. |
ai_model |
string | AI model used for generation. |
owner |
string | Variant owner username. May be an empty string for legacy ownerless rows. |
repo_url |
string | Stored source value. For local generation, this is the submitted repo_path. |
status |
string | Variant status. See the status table below. |
current_stage |
string or null |
Current generation stage, or null when not set. |
last_commit_sha |
string or null |
Commit SHA used for the most recent successful generation. |
last_generated |
string or null |
Last successful generation timestamp in YYYY-MM-DD HH:MM:SS format. |
page_count |
integer | Current or final page count. |
error_message |
string or null |
Terminal error text for error or aborted variants. |
plan_json |
string or null |
Stringified JSON plan. This field is not parsed for you. |
generation_id |
string or null |
Hyphenated UUID that identifies the variant. |
created_at |
string | Row creation timestamp in YYYY-MM-DD HH:MM:SS format. |
updated_at |
string | Last update timestamp in YYYY-MM-DD HH:MM:SS format. |
Status values:
| Value | Description |
|---|---|
generating |
Generation is active. |
ready |
A rendered site is available. |
error |
Generation failed. |
aborted |
Generation was cancelled. |
current_stage values:
| Value | Appears in | Description |
|---|---|---|
cloning |
HTTP, WebSocket progress |
Cloning or opening the source repository. |
incremental_planning |
HTTP, WebSocket progress |
Selecting pages for incremental regeneration. |
planning |
HTTP, WebSocket progress |
Building the initial page plan. |
generating_pages |
HTTP, WebSocket progress |
Generating page markdown. |
validating |
HTTP, WebSocket progress |
Validating generated pages. |
cross_linking |
HTTP, WebSocket progress |
Fixing and adding internal links. |
rendering |
HTTP, WebSocket progress |
Rendering the final static site. |
up_to_date |
HTTP only | The variant already matched the current commit and was marked ready without regenerating content. |
null |
HTTP | No stage is currently set. |
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "admin",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": "{\"project_name\":\"for-testing-only\",\"tagline\":\"Test repo\",\"navigation\":[]}",
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
Returns a complete stored variant record. See Tracking Generation Progress for the dashboard view of these states.
ProjectsCollection object
Used by GET /api/projects, GET /api/status, and WebSocket sync.
| Name | Type | Description |
|---|---|---|
projects |
array of ProjectVariant |
Visible variants for the caller, including non-ready variants. |
known_models |
object | Ready models grouped by provider. This list is global, not access-filtered. |
known_branches |
object | Ready branches grouped by project name. Admins see all owners; non-admin callers see only their own ready branches. |
{
"projects": [
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "admin",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
],
"known_models": {
"claude": ["opus"],
"cursor": ["gpt-5.4-xhigh-fast"]
},
"known_branches": {
"for-testing-only": ["main", "dev"]
}
}
Returns a full snapshot for listing and refresh flows.
Error body
Most explicit HTTP errors use a detail field. Request validation failures use FastAPI's default 422 body, where detail is an array.
{"detail": "Unauthorized"}
{
"detail": [
{
"type": "value_error",
"loc": ["body", "branch"],
"msg": "Value error, Invalid branch name: 'release/1.0'. Branch names cannot contain slashes — use hyphens instead (e.g., release-1.x).",
"input": "release/1.0"
}
]
}
Returns machine-readable error data for non-2xx responses.
Health and discovery
GET /health
Public health check.
Auth: Public
No parameters.
curl http://localhost:8000/health
{"status": "ok"}
Returns 200 OK when the service is up.
GET /api/models
Public discovery endpoint for supported providers, server defaults, and known ready models.
Auth: Public
No parameters.
Response body:
| Name | Type | Description |
|---|---|---|
providers |
array of strings | Supported provider IDs. The current set is claude, gemini, cursor. |
default_provider |
string | Server default provider used when POST /api/generate omits ai_provider. |
default_model |
string | Server default model used when POST /api/generate omits ai_model. |
known_models |
object | Ready models grouped by provider. |
curl http://localhost:8000/api/models
{
"providers": ["claude", "gemini", "cursor"],
"default_provider": "cursor",
"default_model": "gpt-5.4-xhigh-fast",
"known_models": {
"claude": ["opus"],
"gemini": ["pro"]
}
}
Returns 200 OK. known_models is derived from ready variants only.
Authentication endpoints
POST /api/auth/login
Create a session cookie and return the authenticated identity.
Auth: Public
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
username |
string | none | Login name. Use admin only when authenticating with the bootstrap ADMIN_KEY. |
api_key |
string | none | Bootstrap ADMIN_KEY or a stored user API key. |
Response body:
| Name | Type | Description |
|---|---|---|
username |
string | Authenticated username. |
role |
string | admin, user, or viewer. |
is_admin |
boolean | true for the bootstrap admin identity and DB-backed admin users. |
Response headers:
| Name | Value | Description |
|---|---|---|
Set-Cookie |
docsfy_session=... |
Creates the docsfy_session cookie for browser and WebSocket session auth. |
curl -i -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","api_key":"<ADMIN_KEY>"}'
{
"username": "admin",
"role": "admin",
"is_admin": true
}
Returns 200 OK and sets docsfy_session. For DB users, username must match the owner of the submitted API key. Returns 400 for malformed or non-object JSON, and 401 for invalid credentials.
POST /api/auth/logout
Delete the current session cookie and remove its server-side session row if present.
Auth: Public
No parameters.
curl -X POST \
-b "docsfy_session=<SESSION_TOKEN>" \
http://localhost:8000/api/auth/logout
{"ok": true}
Returns 200 OK. The response always clears docsfy_session, even when no valid session existed.
GET /api/auth/me
Return the current authenticated identity.
Auth: Bearer token or session cookie
No parameters.
curl -H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/auth/me
{
"username": "alice",
"role": "viewer",
"is_admin": false
}
Returns 200 OK with the active identity, or 401 when unauthenticated.
POST /api/auth/rotate-key
Rotate the current DB-backed user's API key.
Auth: Bearer token or session cookie
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
new_key |
string | auto-generated | Optional replacement API key. Must be at least 16 characters when provided. |
Response body:
| Name | Type | Description |
|---|---|---|
username |
string | Rotated username. |
new_api_key |
string | New raw API key. |
Response headers:
| Name | Value | Description |
|---|---|---|
Cache-Control |
no-store |
Prevents caching of the returned secret. |
Set-Cookie |
expired docsfy_session |
Clears the current session cookie. |
curl -X POST http://localhost:8000/api/auth/rotate-key \
-b "docsfy_session=<SESSION_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"new_key":"my-very-secure-custom-password-123"}'
{
"username": "alice",
"new_api_key": "my-very-secure-custom-password-123"
}
Returns 200 OK, invalidates all sessions for that user, and clears the caller's docsfy_session. Returns 400 for malformed JSON, non-object JSON, short custom keys, or when the caller is the bootstrap ADMIN_KEY identity.
Project listing and lookup
GET /api/projects and GET /api/status
List visible project variants and the known ready models and branches.
Auth: Bearer token or session cookie
No parameters.
curl -H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/projects
{
"projects": [
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "alice",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
],
"known_models": {
"claude": ["opus"]
},
"known_branches": {
"for-testing-only": ["main", "dev"]
}
}
Returns a ProjectsCollection object. Admins see all variants. Non-admin callers see owned variants plus any variants shared with them. GET /api/status is a direct alias of GET /api/projects.
GET /api/projects/by-id/{generation_id}
Look up a variant by generation UUID.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
generation_id |
string | none | Hyphenated UUID from POST /api/generate or a stored ProjectVariant.generation_id. |
curl -H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/projects/by-id/5bf1495b-b6fa-4318-841c-dced628a2c5b
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "alice",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
Returns a ProjectVariant object. Returns 400 for invalid UUID format and 404 when the generation ID does not exist or is not visible to the caller.
GET /api/projects/{name}
List all visible variants for one project name.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. Must start with an alphanumeric character and can contain letters, digits, ., _, and -. |
curl -H "Authorization: Bearer <ADMIN_KEY>" \
http://localhost:8000/api/projects/for-testing-only
{
"name": "for-testing-only",
"variants": [
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "alice",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
]
}
Returns all visible variants for that project name. Admins see all owners. Non-admin callers see owned variants plus shared variants. Returns 404 when no visible variants exist.
GET /api/projects/{name}/{branch}/{provider}/{model}
Look up one variant by project name, branch, provider, and model.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
branch |
string | none | Branch name. Branches cannot contain / and must match ^[a-zA-Z0-9][a-zA-Z0-9._-]*$. |
provider |
string | none | Stored AI provider ID. |
model |
string | none | Stored AI model ID. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Admin-only owner disambiguation. Ignored for non-admin callers. |
curl -H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/projects/for-testing-only/main/claude/opus?owner=alice"
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "alice",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
Returns a ProjectVariant object. Returns 404 when the variant does not exist or is not visible, and 409 when an admin lookup is ambiguous across multiple owners.
Generation and control
POST /api/generate
Start documentation generation for a remote Git repository or an admin-supplied local Git path.
Auth: admin or user
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
repo_url |
string | none | Remote Git URL. Accepted forms are http://host/org/repo, https://host/org/repo, and git@host:org/repo, with optional .git. Exactly one of repo_url or repo_path is required. |
repo_path |
string | none | Absolute local Git repository path. Admin only. Exactly one of repo_url or repo_path is required. |
ai_provider |
string | server default | AI provider. Valid values: claude, gemini, cursor. |
ai_model |
string | server default | AI model name. |
ai_cli_timeout |
integer | server default | Per-call AI CLI timeout, in seconds. Must be greater than 0. |
force |
boolean | false |
Force full regeneration instead of reusing cached content. |
branch |
string | main |
Branch to generate. Slashes are rejected. |
Note: The request body does not include a
project_namefield. docsfy derives the project name fromrepo_urlor the basename ofrepo_path.Warning:
repo_urltargets that point to localhost or private network addresses are rejected.Warning:
repo_pathmust exist, be absolute, and contain a.gitdirectory.
Common rejection cases:
| Condition | Status | Result |
|---|---|---|
Neither repo_url nor repo_path provided |
422 |
FastAPI validation error |
Both repo_url and repo_path provided |
422 |
FastAPI validation error |
Invalid repo_url, non-absolute repo_path, or invalid branch |
422 |
FastAPI validation error |
repo_path used by non-admin caller |
403 |
Rejected before local path lookup |
repo_path does not exist or is not a Git repo |
400 |
Request rejected |
Invalid ai_provider |
400 |
Request rejected |
| Same owner/name/branch/provider/model already generating | 409 |
Duplicate active generation |
Response body:
| Name | Type | Description |
|---|---|---|
project |
string | Derived project name. |
status |
string | Always generating on acceptance. |
branch |
string | Resolved branch for the new run. |
generation_id |
string | Hyphenated UUID for the variant. |
curl -X POST http://localhost:8000/api/generate \
-H "Authorization: Bearer <USER_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"repo_url": "https://github.com/myk-org/for-testing-only",
"ai_provider": "claude",
"ai_model": "opus",
"branch": "main",
"force": false
}'
{
"project": "for-testing-only",
"status": "generating",
"branch": "main",
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b"
}
Returns 202 Accepted. The server creates or updates the variant row immediately, then sends WebSocket progress, status_change, and sync messages to admins, the project owner, and users with access to that project/owner pair.
POST /api/projects/{name}/abort
Abort the only active generation matching a project name.
Auth: admin or user
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
No query or body parameters.
Warning: This route is not deterministic when more than one active variant exists for the same project name. Use the variant-scoped abort route for automation.
curl -X POST \
-H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/projects/for-testing-only/abort
{"aborted": "for-testing-only"}
Returns 200 OK when one matching active generation is cancelled. Non-admin callers can abort only their own active runs. Returns 404 when no active generation exists and 409 when more than one active variant exists or cancellation has not completed yet.
POST /api/projects/{name}/{branch}/{provider}/{model}/abort
Abort one active variant.
Auth: admin or user
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
branch |
string | none | Branch name. |
provider |
string | none | AI provider. |
model |
string | none | AI model. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Admin-only owner disambiguation when the active variant belongs to another owner or multiple owners have the same active variant. Ignored for non-admin callers. |
curl -X POST \
-H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/projects/for-testing-only/main/claude/opus/abort?owner=alice"
{"aborted": "for-testing-only/main/claude/opus"}
Returns 200 OK when the matching task is cancelled. Returns 404 when no active generation matches, and 409 when the lookup is ambiguous or cancellation is still in progress. Successful aborts produce a terminal status_change and a follow-up sync.
Deletion
DELETE /api/projects/{name}
Delete all variants for one project name.
Auth: admin or user
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Required for admin callers. Ignored for non-admin callers, who can delete only their own variants. Use an empty value (?owner=) to target a legacy ownerless row. |
curl -X DELETE \
-H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/projects/for-testing-only?owner=alice"
{"deleted": "for-testing-only"}
Returns 200 OK after deleting all variants for the target owner and project name. Returns 404 when nothing matches and 409 when any matching variant is still generating. A successful delete sends a WebSocket sync.
DELETE /api/projects/{name}/{branch}/{provider}/{model}
Delete one variant.
Auth: admin or user
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
branch |
string | none | Branch name. |
provider |
string | none | AI provider. |
model |
string | none | AI model. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Required for admin callers. Ignored for non-admin callers, who can delete only their own variants. Use an empty value (?owner=) to target a legacy ownerless row. |
curl -X DELETE \
-H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/projects/for-testing-only/main/claude/opus?owner=alice"
{"deleted": "for-testing-only/main/claude/opus"}
Returns 200 OK after deleting the matching variant. Returns 404 when the variant does not exist and 409 when the variant is still generating. A successful delete sends a WebSocket sync.
Downloads and generated files
GET /api/projects/{name}/download
Download a tarball for the newest accessible ready variant of a project.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
No query parameters.
Warning: This route resolves the newest accessible ready variant. For deterministic automation, use the variant-scoped download route.
Response headers:
| Name | Value | Description |
|---|---|---|
Content-Type |
application/gzip |
Gzip-compressed tar archive. |
Content-Disposition |
attachment; filename="<name>-docs.tar.gz" |
Suggested download filename. |
curl -OJ \
-H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/api/projects/for-testing-only/download
Returns the generated site as a tarball. Returns 404 when no accessible ready variant exists or the site directory is missing, and may return 409 when the newest accessible variant is ambiguous across owners.
GET /api/projects/{name}/{branch}/{provider}/{model}/download
Download a tarball for one variant.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
branch |
string | none | Branch name. |
provider |
string | none | AI provider. |
model |
string | none | AI model. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Admin-only owner disambiguation when more than one owner has the same variant. Ignored for non-admin callers. |
Response headers:
| Name | Value | Description |
|---|---|---|
Content-Type |
application/gzip |
Gzip-compressed tar archive. |
Content-Disposition |
attachment; filename="<name>-<branch>-<provider>-<model>-docs.tar.gz" |
Suggested download filename. |
curl -OJ \
-H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/projects/for-testing-only/main/claude/opus/download?owner=alice"
Returns the generated site for that variant. Returns 400 when the variant exists but is not ready, 404 when the variant or site is missing, and 409 when an admin lookup is ambiguous across owners.
GET /docs/{project}/{path:path}
Serve a file from the newest accessible ready variant.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
project |
string | none | Project name. |
path |
string | index.html when the resolved path is empty |
File path inside the generated site, such as index.html, search-index.json, or assets/style.css. |
No query parameters.
Warning: This route resolves the newest accessible ready variant. For deterministic automation, use the variant-scoped docs route.
curl -H "Authorization: Bearer <USER_API_KEY>" \
http://localhost:8000/docs/for-testing-only/search-index.json
Returns raw file bytes from the generated site. Returns 404 when no accessible docs are available or the file does not exist, 403 when the resolved file path escapes the generated site directory, and may return 409 when the newest accessible variant is ambiguous across owners.
GET /docs/{project}/{branch}/{provider}/{model}/{path:path}
Serve a file from one variant.
Auth: Bearer token or session cookie
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
project |
string | none | Project name. |
branch |
string | none | Branch name. |
provider |
string | none | AI provider. |
model |
string | none | AI model. |
path |
string | index.html when the resolved path is empty |
File path inside the generated site. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Admin-only owner disambiguation when more than one owner has the same variant. Ignored for non-admin callers. |
curl -H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/docs/for-testing-only/main/claude/opus/index.html?owner=alice"
Returns raw file bytes from the requested variant. Returns 404 when the variant or file does not exist, 403 when the resolved file path escapes the site directory, and 409 when an admin lookup is ambiguous across owners.
Admin endpoints
GET /api/admin/users
List all users.
Auth: admin
No parameters.
Response body:
| Name | Type | Description |
|---|---|---|
users |
array | User rows without API key hashes. Each row contains id, username, role, and created_at. |
curl -H "Authorization: Bearer <ADMIN_KEY>" \
http://localhost:8000/api/admin/users
{
"users": [
{
"id": 1,
"username": "alice",
"role": "user",
"created_at": "2026-04-18 12:00:00"
}
]
}
Returns 200 OK. Non-admin callers receive 403.
POST /api/admin/users
Create a new user and return its raw API key.
Auth: admin
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
username |
string | none | Username. Must be 2-50 characters, start with an alphanumeric character, and use only letters, digits, ., _, and -. admin is reserved. |
role |
string | user |
User role. Valid values: admin, user, viewer. |
Response body:
| Name | Type | Description |
|---|---|---|
username |
string | Created username. |
api_key |
string | New raw API key. |
role |
string | Assigned role. |
Response headers:
| Name | Value | Description |
|---|---|---|
Cache-Control |
no-store |
Prevents caching of the returned secret. |
curl -X POST http://localhost:8000/api/admin/users \
-H "Authorization: Bearer <ADMIN_KEY>" \
-H "Content-Type: application/json" \
-d '{"username":"alice","role":"viewer"}'
{
"username": "alice",
"api_key": "docsfy_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"role": "viewer"
}
Returns 200 OK. Returns 400 for invalid usernames, reserved admin, duplicate users, invalid roles, or malformed JSON.
DELETE /api/admin/users/{username}
Delete a user.
Auth: admin
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
username |
string | none | Existing username to delete. |
curl -X DELETE \
-H "Authorization: Bearer <ADMIN_KEY>" \
http://localhost:8000/api/admin/users/alice
{"deleted": "alice"}
Returns 200 OK after deleting the user, all of their sessions, any owned projects, access grants they received, access grants to their projects, and their project directory. Returns 400 when an admin tries to delete their own account, 404 when the user does not exist, and 409 when that user has an active generation.
POST /api/admin/users/{username}/rotate-key
Rotate another user's API key.
Auth: admin
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
username |
string | none | Existing username to rotate. |
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
new_key |
string | auto-generated | Optional replacement API key. Must be at least 16 characters when provided. |
Response body:
| Name | Type | Description |
|---|---|---|
username |
string | Rotated username. |
new_api_key |
string | New raw API key. |
Response headers:
| Name | Value | Description |
|---|---|---|
Cache-Control |
no-store |
Prevents caching of the returned secret. |
curl -X POST http://localhost:8000/api/admin/users/alice/rotate-key \
-H "Authorization: Bearer <ADMIN_KEY>" \
-H "Content-Type: application/json" \
-d '{"new_key":"admin-chosen-password-long"}'
{
"username": "alice",
"new_api_key": "admin-chosen-password-long"
}
Returns 200 OK and invalidates all sessions for the target user. Returns 400 for invalid custom keys or malformed JSON and 404 when the user does not exist.
GET /api/admin/projects/{name}/access
List users who have access to a project/owner pair.
Auth: admin
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Required project owner. Access grants are scoped by owner, not just by project name. |
curl -H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/admin/projects/for-testing-only/access?owner=alice"
{
"project": "for-testing-only",
"owner": "alice",
"users": ["bob", "carol"]
}
Returns 200 OK with usernames sorted alphabetically. Returns 400 when owner is missing and 403 for non-admin callers.
POST /api/admin/projects/{name}/access
Grant a user read access to all variants of a project owned by one owner.
Auth: admin
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
Body parameters:
| Name | Type | Default | Description |
|---|---|---|---|
username |
string | none | Target username that will receive access. |
owner |
string | none | Required project owner. The grant applies to this name and this owner only. |
curl -X POST http://localhost:8000/api/admin/projects/for-testing-only/access \
-H "Authorization: Bearer <ADMIN_KEY>" \
-H "Content-Type: application/json" \
-d '{"username":"bob","owner":"alice"}'
{
"granted": "for-testing-only",
"username": "bob",
"owner": "alice"
}
Returns 200 OK. Returns 400 for malformed JSON or missing fields, 404 when the target user does not exist or the project/owner pair does not exist, and sends a WebSocket sync to the target user's active connections.
DELETE /api/admin/projects/{name}/access/{username}
Revoke a user access grant for one project/owner pair.
Auth: admin
Path parameters:
| Name | Type | Default | Description |
|---|---|---|---|
name |
string | none | Project name. |
username |
string | none | Username to revoke. |
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
owner |
string | none | Required project owner. |
curl -X DELETE \
-H "Authorization: Bearer <ADMIN_KEY>" \
"http://localhost:8000/api/admin/projects/for-testing-only/access/bob?owner=alice"
{
"revoked": "for-testing-only",
"username": "bob",
"owner": "alice"
}
Returns 200 OK and sends a WebSocket sync to the target user's active connections. Returns 400 when owner is missing and 403 for non-admin callers. This route is idempotent: it does not error when the grant is already absent.
WebSocket
WebSocket /api/ws
Real-time stream of project snapshots and generation updates.
Auth: docsfy_session cookie or ?token=<api_key>
Query parameters:
| Name | Type | Default | Description |
|---|---|---|---|
token |
string | none | Optional raw ADMIN_KEY or user API key. Use this for non-browser clients that cannot send the session cookie. |
Connection behavior:
| Item | Value |
|---|---|
| Initial server message | sync |
| Server heartbeat | {"type":"ping"} every 30 seconds |
| Required client response | {"type":"pong"} |
| Pong timeout | 10 seconds |
| Max missed pongs | 2 |
| Unauthenticated close code | 1008 |
| Missed-pong close code | 1001 |
| Broadcast recipients | Admins, the project owner, and users granted access to that project/owner pair |
const ws = new WebSocket("ws://localhost:8000/api/ws?token=<USER_API_KEY>");
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "ping") {
ws.send(JSON.stringify({ type: "pong" }));
return;
}
console.log(message);
};
Opens a live subscription. The server sends a full sync immediately after connect, then incremental messages as projects change.
sync message
Full snapshot message.
Fields:
| Name | Type | Description |
|---|---|---|
type |
string | Always sync. |
projects |
array of ProjectVariant |
Full visible project snapshot. |
known_models |
object | Same structure as GET /api/projects. |
known_branches |
object | Same structure as GET /api/projects. |
{
"type": "sync",
"projects": [
{
"name": "for-testing-only",
"branch": "main",
"ai_provider": "claude",
"ai_model": "opus",
"owner": "alice",
"repo_url": "https://github.com/myk-org/for-testing-only",
"status": "ready",
"current_stage": null,
"last_commit_sha": "abc123def456",
"last_generated": "2026-04-18 12:34:56",
"page_count": 12,
"error_message": null,
"plan_json": null,
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b",
"created_at": "2026-04-18 12:00:00",
"updated_at": "2026-04-18 12:34:56"
}
],
"known_models": {
"claude": ["opus"]
},
"known_branches": {
"for-testing-only": ["main", "dev"]
}
}
Sent immediately after connect and again after access changes, deletions, and terminal generation refreshes.
progress message
Incremental update for an in-progress generation.
Fields:
| Name | Type | Description |
|---|---|---|
type |
string | Always progress. |
name |
string | Project name. |
branch |
string | Branch name. |
provider |
string | AI provider. |
model |
string | AI model. |
owner |
string | Variant owner. |
status |
string | Current in-progress status. The current implementation sends generating. |
current_stage |
string, optional | Current stage, such as cloning, planning, or generating_pages. |
page_count |
integer, optional | Current generated page count. |
plan_json |
string, optional | Stringified plan JSON once planning is available. |
error_message |
string, optional | Error text when present during an in-progress update. |
generation_id |
string, optional | Variant UUID. |
{
"type": "progress",
"name": "for-testing-only",
"branch": "main",
"provider": "claude",
"model": "opus",
"owner": "alice",
"status": "generating",
"current_stage": "generating_pages",
"page_count": 4,
"plan_json": "{\"project_name\":\"for-testing-only\",\"tagline\":\"Test repo\",\"navigation\":[]}",
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b"
}
Sent during non-terminal stages. Clients should merge this message by the tuple (name, branch, provider, model, owner).
status_change message
Terminal update for a variant.
Fields:
| Name | Type | Description |
|---|---|---|
type |
string | Always status_change. |
name |
string | Project name. |
branch |
string | Branch name. |
provider |
string | AI provider. |
model |
string | AI model. |
owner |
string | Variant owner. |
status |
string | Terminal status: ready, error, or aborted. |
page_count |
integer, optional | Final page count when available. |
last_generated |
string, optional | Completion timestamp when status is ready. |
last_commit_sha |
string, optional | Final commit SHA when available. |
error_message |
string, optional | Error or abort text when available. |
generation_id |
string, optional | Variant UUID. |
{
"type": "status_change",
"name": "for-testing-only",
"branch": "main",
"provider": "claude",
"model": "opus",
"owner": "alice",
"status": "ready",
"page_count": 12,
"last_generated": "2026-04-18 12:34:56",
"last_commit_sha": "abc123def456",
"generation_id": "5bf1495b-b6fa-4318-841c-dced628a2c5b"
}
Sent when a variant reaches a terminal state. A full sync may follow.
ping message
Server heartbeat message.
Fields:
| Name | Type | Description |
|---|---|---|
type |
string | Always ping. |
{"type": "ping"}
Sent every 30 seconds per open connection. Clients should respond with pong.
pong message
Client heartbeat response.
Fields:
| Name | Type | Description |
|---|---|---|
type |
string | Always pong. |
{"type": "pong"}
Acknowledges the most recent server ping. If the server misses 2 consecutive pongs, it closes the connection with code 1001.