Authentication
All routes under \/api/admin`` require admin privileges. docsfy recognizes two kinds of admin credentials:
- the built-in
adminaccount, authenticated with theADMIN_KEYenvironment variable - a database-backed user created with role
admin
For scripts and automation, send the key as Authorization: Bearer <token>. For browser-based admin flows, log in through POST /api/auth/login; successful logins set an HttpOnly docsfy_session cookie with SameSite=strict, an 8-hour lifetime, and a secure flag controlled by SECURE_COOKIES.
The server-side auth settings come directly from .env.example:
# Required: Admin password (minimum 16 characters)
ADMIN_KEY=
# AI provider and model defaults
# (pydantic_settings reads these case-insensitively)
AI_PROVIDER=cursor
AI_MODEL=gpt-5.4-xhigh-fast
AI_CLI_TIMEOUT=60
# Logging
LOG_LEVEL=INFO
# Data directory for database and generated docs
DATA_DIR=/data
# Cookie security (set to false for local HTTP development)
SECURE_COOKIES=true
Warning:
ADMIN_KEYis not just the built-in admin password. docsfy also uses it as the HMAC secret for stored user API keys, so changingADMIN_KEYinvalidates every existing database-backed user key.Tip: If you are testing over plain
http://on localhost, setSECURE_COOKIES=falseor browser login sessions will not persist.
The bundled docsfy CLI uses the same Bearer-token model under the hood. The admin commands live under docsfy admin users ... and docsfy admin access ....
Quick Reference
| Route | Purpose |
|---|---|
GET /api/admin/users |
List database-backed users |
POST /api/admin/users |
Create a user and return a one-time API key |
DELETE /api/admin/users/{username} |
Delete a user, their sessions, grants, and owned projects |
POST /api/admin/users/{username}/rotate-key |
Generate or set a new API key for a user |
POST /api/admin/projects/{name}/access |
Grant access to a project owned by a specific user |
GET /api/admin/projects/{name}/access?owner=<owner> |
List usernames with access to that project |
DELETE /api/admin/projects/{name}/access/{username}?owner=<owner> |
Revoke access from a user |
Common status codes:
200: request succeeded400: malformed JSON, missing required fields, invalid role, invalid username, or too-short custom key401: no valid Bearer token or session cookie403: authenticated, but not an admin404: target user or project was not found409: delete was blocked because the user currently has a generation in progress
User Management
Create A User
Use POST /api/admin/users to create a database-backed account.
Request body:
username: requiredrole: optional, defaults touser
Valid roles are:
admin: full admin privilegesuser: normal read/write project operationsviewer: read-only access; can sign in and view granted docs, but cannot generate or delete projects
Username rules are strict:
- 2 to 50 characters
- letters and digits first
._and-are allowedadminis reserved, case-insensitively
The admin API tests exercise user creation like this:
response = await admin_client.post(
"/api/admin/users",
json={"username": "testuser", "role": "user"},
)
assert response.status_code == 200
data = response.json()
assert data["username"] == "testuser"
assert data["role"] == "user"
assert data["api_key"].startswith("docsfy_")
assert response.headers.get("cache-control") == "no-store"
A successful response includes:
usernameroleapi_key
The returned api_key is the raw secret. docsfy stores only a hash in the database.
Warning: Treat the returned
api_keyas a one-time secret.GET /api/admin/usersdoes not return raw keys later.Note: The built-in
adminaccount is not a database user. You will not see it inGET /api/admin/users, and you cannot create another user namedadmin.
List Users
Use GET /api/admin/users to retrieve the current database-backed user list.
Each entry includes:
idusernamerolecreated_at
This route intentionally does not return api_key or api_key_hash.
Delete A User
Use DELETE /api/admin/users/{username} to permanently remove a user.
Important behavior:
- the request is rejected if you try to delete the account you are currently authenticated as
- the request returns
404if the user does not exist - the request returns
409if that user currently has a generation in progress - all sessions for that user are invalidated
- project-access grants for that user are removed
- access entries for projects owned by that user are removed
- projects owned by that user are deleted
Warning: Deleting a user is destructive. In docsfy, this is more than removing a login: it also removes the user's owned projects and related access-control entries.
Rotate A User Key
Use POST /api/admin/users/{username}/rotate-key to replace a user's API key.
Request body:
- empty JSON object to generate a new random key
- or
{ "new_key": "..." }to set a custom key
Custom keys must be at least 16 characters long.
The auth tests cover an admin-set custom key like this:
custom = "admin-chosen-password-long"
resp = await admin_client.post(
"/api/admin/users/admin-custom-target/rotate-key",
json={"new_key": custom},
)
assert resp.status_code == 200
assert resp.json()["new_api_key"] == custom
# Verify the custom key works
user = await get_user_by_key(custom)
assert user is not None
assert user["username"] == "admin-custom-target"
A successful response includes:
usernamenew_api_key
This response is sent with Cache-Control: no-store, and all existing sessions for that user are invalidated.
If the target user does not exist, the route returns 404.
Note: There is currently no separate admin endpoint for changing a user's role after creation. Roles are assigned when the user is created.
Project Access Administration
Project sharing in docsfy is grant-based. An admin can give one user access to a project owned by another user without transferring ownership.
Access is scoped by:
- project name
- project owner
Access is not scoped by:
- branch
- AI provider
- AI model
That means one grant covers every variant of that project for that owner.
Note: In these endpoints,
ownermeans the project owner's username, not the admin making the request.
A granted user sees shared projects through the normal project listing and docs routes. Their role still matters: a viewer with a grant can open docs, but remains read-only.
Grant Access
Use POST /api/admin/projects/{name}/access.
Request body:
username: requiredowner: required
Before creating the grant, docsfy verifies that:
- the target user exists
- the project exists for the specified owner
The route implementation shows the exact validation and response shape:
# Validate user exists
user = await get_user_by_username(username)
if not user:
raise HTTPException(status_code=404, detail=f"User '{username}' not found")
# Validate project exists for the specified owner
variants = await list_variants(name, owner=project_owner)
if not variants:
raise HTTPException(
status_code=404,
detail=f"Project '{name}' not found for owner '{project_owner}'",
)
await grant_project_access(name, username, project_owner=project_owner)
await notify_access_change(username)
logger.info(
f"[AUDIT] Admin '{request.state.username}' granted '{username}' access to '{name}' (owner: '{project_owner}')"
)
return {"granted": name, "username": username, "owner": project_owner}
Use this route when you want a user to see a project they do not own, including all of that project's variants for the chosen owner.
Look Up Current Access
Use GET /api/admin/projects/{name}/access?owner=<owner> to see who currently has access to a project.
The response shape is:
projectownerusers
users is a username list, ordered alphabetically.
This is the admin access-lookup endpoint exposed by the API today. It is project-centric: there is no separate admin route that lists every grant for a given user.
Revoke Access
Use DELETE /api/admin/projects/{name}/access/{username}?owner=<owner> to remove a grant.
The response includes:
revokedusernameowner
You must still provide owner, because grants are stored per project-owner pair.
This route is effectively idempotent for automation. It always returns the same summary object and does not distinguish between "grant was present and removed" and "grant was already absent."
Tip: Grant and revoke operations trigger a sync update for the affected user, so connected dashboards usually reflect the change without a manual refresh.
Related Auth Endpoints
A few non-admin auth routes matter when you are operating the Admin API.
Log In
Use POST /api/auth/login with JSON:
usernameapi_key
For the built-in admin account, username must be exactly admin and api_key must match ADMIN_KEY.
For database-backed users, the username must match the owner of the supplied API key.
A successful login returns:
usernameroleis_admin
It also sets the docsfy_session cookie for browser use.
Check Current Identity
Use GET /api/auth/me to verify who the server thinks you are. It returns:
usernameroleis_admin
This is the quickest way to confirm whether a token is being treated as built-in admin, database admin, normal user, or viewer.
Log Out
Use POST /api/auth/logout to clear the current session cookie and delete the matching server-side session record.
Self-Service Key Rotation
Use POST /api/auth/rotate-key when a database-backed user needs to rotate their own key.
This route accepts the same optional new_key field as the admin rotate route. On success, it:
- returns
usernameandnew_api_key - sends
Cache-Control: no-store - deletes the current session cookie
- invalidates the user's existing sessions, so they must log in again with the new key
Warning: The built-in
adminaccount cannot usePOST /api/auth/rotate-key. Manage that credential by changingADMIN_KEYin your deployment instead.