Every dashboard REST route under /api, which credentials it accepts, and what an API key cannot do.
Dashboard automation uses /api on the same origin as inference. Most management routes accept a DurinDoor API key. Secret reveal, database administration, process control, and local host operations have stricter credentials. See Automating DurinDoor for a workflow and MCP control for JSON-RPC tools.
Authentication applies to the decoded route path. Individual handlers can require additional credentials or local access.
Code
Who
session
Dashboard JWT cookie auth_token
CLI
Header x-9r-cli-token (machine-bound)
key
DurinDoor API key: Authorization: Bearer, x-api-key, x-goog-api-key, or ?key=
loop
Loopback peer and requireLogin is off
none
Public. No credential
gw
MCP gateway key, not a DurinDoor API key
local
Local-only gate first. Remote callers without CLI get HTTP 403 Local only: CLI token required
Loopback identity comes from wrapper-stamped peer headers, not from Host alone. A request through x-9r-via-proxy is never loopback. Management routes accept session, CLI, key, or loop. Reveal paths reject a key. Always-protected paths (/api/shutdown, /api/settings/database, /api/version/shutdown, /api/version/update, OAuth auto-import) accept session or CLI only. Other protected routes fall through to session, CLI, or loop. POST /api/mcp/control is a separate branch: session, CLI, or key, plus loopback with no credential when requireApiKey is off and Origin is absent or same-origin. Gateway protocol surfaces (/api/mcp-gateway, /sse, /message) take a gateway key, CLI, or loopback, not a DurinDoor API key. Details: MCP control.
A DurinDoor API key is an inference credential. It cannot reveal stored secrets, shut the process down, or change login and proxy settings. Mint a dedicated key for automation and keep reveal, shutdown, and database work on a dashboard session.
Default media routes: GET lists each endpoint's saved order, available models, and effective order; PUT { kind, models } saves one (empty list = automatic). See Media routes
GET optional provider query returns roster/status and pruned references; POST optional JSON { provider } forces eligible provider sync or all enabled providers
session, CLI, key, loop
GET, PUT
/api/models
Dashboard model list with aliases (GET); set one alias (PUT)
session, CLI, key, loop
GET, PUT, DELETE
/api/models/alias
List, set, or delete model aliases
session, CLI, key, loop
GET, POST
/api/models/availability
List model cooldowns (GET); POST clearCooldown
session, CLI, key, loop
GET, POST, PATCH, DELETE
/api/models/custom
CRUD custom model rows and capabilities
session, CLI, key, loop
GET, POST, DELETE
/api/models/disabled
List, set, or clear disabled model ids per provider
session, CLI, key, loop
GET, PUT, DELETE
/api/models/enabled
List, set, or clear the visible-model allowlist per provider
session, CLI, key, loop
POST
/api/models/test
Ping one model by kind. Optional connectionId pins the probe to an active account; unavailable pins fail without switching accounts. API keys cannot request a pin because internal probes use an unscoped CLI token
unpinned: session, CLI, key, loop; pinned: session, CLI, open local dashboard (operator only)
POST
/api/models/test/batch
Ping up to 200 models; streamed results. Optional connectionId pins each probe to an active account with the same operator restriction
unpinned: session, CLI, key, loop; pinned: session, CLI, open local dashboard (operator only)
Runtime status, 24-hour activity, and 7-day provider health from recorded usage
session, CLI, key, loop
POST
/api/usage/{connectionId}/claude-reset
JSON { grantId } consumes one Claude OAuth reset grant; irreversible, not a read-only quota probe
session, CLI, key, loop
GET
/api/usage/chart
Usage chart buckets for a period
session, CLI, key, loop
GET
/api/usage/combos
Combo-attributed usage for a period
session, CLI, key, loop
GET
/api/usage/history
Paginated usage rows (limit up to 200, offset, filters)
session, CLI, key, loop
GET
/api/usage/logs
Last 200 usage log rows
session, CLI, key, loop
GET
/api/usage/me
Self-service usage for the calling API key only: lifetime totals, daily limit status, policy, and quota snapshots for the key's own connections. ?format=json is accepted; requires a resolvable application API key, not just session/CLI/loop
key
GET
/api/usage/providers
Distinct providers from request details
session, CLI, key, loop
GET
/api/usage/request-details
Paginated request-detail rows
session, CLI, key, loop
GET
/api/usage/request-logs
Last 200 usage log rows (alias of logs)
session, CLI, key, loop
POST
/api/usage/reset
Delete usage history for a period
session, CLI, key, loop
GET
/api/usage/stats
Aggregate usage for a period or date range
session, CLI, key, loop
GET
/api/usage/stream
SSE of full stats for a dashboard period
session, CLI, key, loop
GET
/api/usage/{connectionId}
Live provider quota/usage for one connection; ?force=1
Streamed JSON export of requestDetails rows. ?hours=1-168 (default 24), ?limit=1-50000 (default 10000). Truncated exports carry capped, limit, and totalAvailable in the response header, ahead of the row array, so a streaming reader learns about truncation before it finishes the body
Read settings (GET) or patch them (PATCH), including the mediaRoutes key (400 Invalid mediaRoutes on a malformed value)
session, CLI, key, loop
GET, POST
/api/settings/auto-configure
Headroom/PxPipe/Firecrawl probe status or run
session, CLI, key, loop; handler wants session
GET, POST
/api/settings/database
Export/import the data file; JWT or CLI plus password
session, CLI; dashboard password required
POST
/api/settings/database/cutover
Run SQLite to PostgreSQL cutover; 503 + Retry-After: 5 if the lock is held
session, CLI; dashboard password required
GET, POST
/api/settings/database/engine
Read engine status (servingFallback; never postgresUrl) or patch version/features
session, CLI; password
GET
/api/settings/database/log
Recent pgCutoverLog rows
session, CLI; dashboard password required
POST
/api/settings/database/rollback
Restore an allowlisted cutover snapshot; 409 rollback_requires_force unless force: true
session, CLI; password
POST
/api/settings/database/selective
Selective provider/combo transfer; preview is secret-free
session, CLI; dashboard password required
POST
/api/settings/database/test
Probe a candidate Postgres URL without opening the adapter
session, CLI; dashboard password required
POST
/api/settings/firecrawl/detect
Probe Firecrawl endpoints; session or open dashboard in handler
session, CLI, key, loop; handler wants session
POST
/api/settings/proxy-test
Test the configured outbound proxy against a fixed URL
session, CLI, key, loop
GET
/api/settings/require-login
Public requireLogin and tunnel access flags
none
Database routes require a dashboard session or CLI token plus the dashboard password. Most engine, cutover, rollback, log, and test calls read the password from x-9r-password. Selective transfer reads JSON password; database export uses x-9r-password, and import reads JSON password. Database JSON imports are limited to 16 MiB.
Save apiKey or mimoPassToken; optional uid, baseUrl, mimoUserId, mimoCUserId, region. Key must start sk-; base URL must use HTTPS on a Xiaomi MiMo host
session, CLI, key, loop
GET
/api/oauth/xiaomi-mimo/auto-import
Import local MiMo Desktop credentials
session, CLI; local
POST
/api/oauth/xiaomi-mimo/login/start
Begin browser login; optional JSON region, default cn
The gate refuses a key on GET /api/keys/{id}/reveal, GET /api/mcp-gateway/keys/{id}/reveal (also 403 unless loopback), any ?reveal=1 (including an encoded /reveal path), /api/shutdown, /api/version/shutdown, /api/version/update, /api/settings/database and children, /api/oauth/cursor/auto-import, /api/oauth/kiro/auto-import, /api/oauth/xiaomi-mimo/auto-import, and remote calls to local-only spawn paths (tunnel enable/disable, Tailscale, Cowork settings, Antigravity MITM, Headroom start/stop/proxy/extras, MCP plugin /sse and /message, POST /api/auth/reset-password). Families that do not accept an inference key: /api/key-groups, /api/data-retention, /api/compression, /api/token-saver, /api/pxpipe.
After the gate, GET/POST /api/settings/auto-configure and POST /api/settings/firecrawl/detect still want a session (or requireLogin off). POST /api/data-retention needs JWT or CLI. POST /api/mcp-gateway/keys is local-only in the handler. POST /api/shutdown also needs SHUTDOWN_SECRET and returns 403 in production.
Gateway OAuth leaves: client-metadata is public. callback takes session, CLI, or loop. authorize and status sit on the management gate, so a key can finish a connect flow.
PATCH /api/settings always drops password, passwordSessionEpoch, mitmSudoEncrypted, postgresUrl, mfaEnabled, mfaSecret, and mfaBackupCodes. GET /api/settings withholds postgresUrl, mfaSecret, and mfaBackupCodes from every caller, including an operator session: the libpq URL carries user:password@. Without a session or CLI it also drops these login and proxy settings: exposeComboOnly, requireLogin, requireApiKey, authMode, oidcIssuerUrl, oidcClientId, oidcClientSecret, oidcScopes, oidcLoginLabel, tunnelDashboardAccess, enableObservability, outboundProxyEnabled, outboundProxyUrl, outboundNoProxy, claudeClassifierCompat. claudeAutoPing and codexAutoPing return 400 (Auto-ping must be updated through the connection-scoped endpoint). MCP update_settings strips the same secret set and auth-critical set plus those auto-ping keys, and returns 400 No updatable settings provided when nothing remains. POST /api/settings/database/engine also strips postgresUrl, password, passwordSessionEpoch, oidcClientSecret, and mitmSudoEncrypted from the returned settings object. POST /api/settings/database/rollback accepts { snapshotPath?, force } and returns { error: "rollback_requires_force", warning, discardedSince } without force: true. POST /api/settings/database/cutover returns { error: "A cutover is already in flight; retry in a moment." } when the lock is held.
An API key sees host and port. user:password@ becomes ***. Unparsable strings become ***. Session, CLI, and an open local dashboard keep the raw value so an edit form does not persist the placeholder. Fields: outboundProxyUrl on settings, proxyUrl on proxy pools, providerSpecificData.connectionProxyUrl on providers, and headroomUrl on GET /api/headroom/status. MCP get_settings redacts outboundProxyUrl. MCP list_connections drops connectionProxyUrl instead of redacting it.
Duplicate API-key connection create; rollback after cutover without force: true
500
{ error: "Failed to ..." }
Handler catch-all
Malformed percent-encoding on an /api path is 401, fail-closed. Combo create and key create are 201. Key create includes key; later list/detail return maskedKey.
Add an API-key connection and read quota. POST needs provider and name. apiKey is required unless the provider is noAuth or ollama-local. OAuth accounts use /api/oauth/.... GET /api/usage/{connectionId} returns provider quota for OAuth or a usage-eligible API-key provider. ?force=1 skips that provider's in-process cache. MCP snapshot refresh is refresh_quota.