Management API
Base path: http://localhost:8317/v0/management
This API manages the CLI Proxy API’s runtime configuration and authentication files. All changes are persisted to the YAML config file and hot‑reloaded by the service.
Note: The following options cannot be modified via API and must be set in the config file (restart if needed):
allow-remote-managementremote-management-key(if plaintext is detected at startup, it is automatically bcrypt‑hashed and written back to the config)
Authentication
- All requests (including localhost) must provide a valid management key.
- Remote access requires enabling remote management in the config:
allow-remote-management: true. - Provide the management key (in plaintext) via either:
Authorization: Bearer <plaintext-key>X-Management-Key: <plaintext-key>
Additional notes:
- Setting the
MANAGEMENT_PASSWORDenvironment variable registers an additional plaintext management secret and forces remote management to stay enabled even whenallow-remote-managementis false. The value is never persisted and must be sent via the sameAuthorization/X-Management-Keyheaders. - When the proxy starts with
cliproxy run --password <pwd>or via the SDK’sWithLocalManagementPassword, localhost clients (127.0.0.1/::1) may present that local-only password through the same headers; it only lives in memory and is not written to disk. - The Management API returns 404 only when both
remote-management.secret-keyis empty andMANAGEMENT_PASSWORDis unset. - For remote IPs, 5 consecutive authentication failures trigger a temporary ban (~30 minutes) before further attempts are allowed.
If a plaintext key is detected in the config at startup, it will be bcrypt‑hashed and written back to the config file automatically.
Request/Response Conventions
- Content-Type:
application/json(unless otherwise noted). - Boolean/int/string updates: request body is
{ "value": <type> }. - Array PUT: either a raw array (e.g.
["a","b"]) or{ "items": [ ... ] }. - Array PATCH: supports
{ "old": "k1", "new": "k2" }or{ "index": 0, "value": "k2" }. - Object-array PATCH: supports matching by index or by key field (specified per endpoint).
Endpoints
Usage Statistics
- GET
/usage— Retrieve aggregated in-memory request metrics- Response:json
{ "usage": { "total_requests": 24, "success_count": 22, "failure_count": 2, "total_tokens": 13890, "requests_by_day": { "2024-05-20": 12 }, "requests_by_hour": { "09": 4, "18": 8 }, "tokens_by_day": { "2024-05-20": 9876 }, "tokens_by_hour": { "09": 1234, "18": 865 }, "apis": { "POST /v1/chat/completions": { "total_requests": 12, "total_tokens": 9021, "models": { "gpt-4o-mini": { "total_requests": 8, "total_tokens": 7123, "details": [ { "timestamp": "2024-05-20T09:15:04.123456Z", "tokens": { "input_tokens": 523, "output_tokens": 308, "reasoning_tokens": 0, "cached_tokens": 0, "total_tokens": 831 } } ] } } } } }, "failed_requests": 2 } - Notes:
- Statistics are recalculated for every request that reports token usage; data resets when the server restarts.
- Hourly counters fold all days into the same hour bucket (
00–23). - The top-level
failed_requestsrepeatsusage.failure_countfor convenience when polling.
- Response:
Config
- GET
/config— Get the full config- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/config - Response:json
{"debug":true,"proxy-url":"","api-keys":["1...5","JS...W"],"ampcode":{"upstream-url":"https://ampcode.com","restrict-management-to-localhost":true},"quota-exceeded":{"switch-project":true,"switch-preview-model":true},"gemini-api-key":[{"api-key":"AI...01","base-url":"https://generativelanguage.googleapis.com","headers":{"X-Custom-Header":"custom-value"},"proxy-url":"","excluded-models":["gemini-1.5-pro","gemini-1.5-flash"]},{"api-key":"AI...02","proxy-url":"socks5://proxy.example.com:1080","excluded-models":["gemini-pro-vision"]}],"request-log":true,"request-retry":3,"claude-api-key":[{"api-key":"cr...56","base-url":"https://example.com/api","proxy-url":"socks5://proxy.example.com:1080","models":[{"name":"claude-3-5-sonnet-20241022","alias":"claude-sonnet-latest"}],"excluded-models":["claude-3-opus"]},{"api-key":"cr...e3","base-url":"http://example.com:3000/api","proxy-url":""},{"api-key":"sk-...q2","base-url":"https://example.com","proxy-url":""}],"codex-api-key":[{"api-key":"sk...01","base-url":"https://example/v1","proxy-url":"","excluded-models":["gpt-4o-mini"]}],"openai-compatibility":[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk...01","proxy-url":""}],"models":[{"name":"moonshotai/kimi-k2:free","alias":"kimi-k2"}]},{"name":"iflow","base-url":"https://apis.iflow.cn/v1","api-key-entries":[{"api-key":"sk...7e","proxy-url":"socks5://proxy.example.com:1080"}],"models":[{"name":"deepseek-v3.1","alias":"deepseek-v3.1"},{"name":"glm-4.5","alias":"glm-4.5"},{"name":"kimi-k2","alias":"kimi-k2"}]}]} - Notes:
- The response no longer includes
generative-language-api-key; useGET /generative-language-api-keyif you need a pure-string view. - When no configuration is loaded yet the handler returns
{}.
- The response no longer includes
- Request:
Latest Version
- GET
/latest-version— Fetch the latest release version string (no asset download)- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/latest-version - Response:json
{ "latest-version": "v1.2.3" } - Notes:
- Data is retrieved from
https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/latestwithUser-Agent: CLIProxyAPI. - When
proxy-urlis set, the request honors that proxy; the endpoint only returns the version value and does not download release assets.
- Data is retrieved from
- Request:
Debug
- GET
/debug— Get the current debug state- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/debug - Response:json
{ "debug": false }
- Request:
- PUT/PATCH
/debug— Set debug (boolean)- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":true}' \ http://localhost:8317/v0/management/debug - Response:json
{ "status": "ok" }
- Request:
Config YAML
- GET
/config.yaml— Download the persisted YAML file as-is- Response headers:
Content-Type: application/yaml; charset=utf-8Cache-Control: no-store
- Response body: raw YAML stream preserving comments/formatting.
- Response headers:
- PUT
/config.yaml— Replace the config with a YAML document- Request:bash
curl -X PUT -H 'Content-Type: application/yaml' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ --data-binary @config.yaml \ http://localhost:8317/v0/management/config.yaml - Response:json
{ "ok": true, "changed": ["config"] } - Notes:
- The server validates the YAML by loading it before persisting; invalid configs return
422with{ "error": "invalid_config", "message": "..." }. - Write failures return
500with{ "error": "write_failed", "message": "..." }.
- The server validates the YAML by loading it before persisting; invalid configs return
- Request:
Logging to File
- GET
/logging-to-file— Check whether file logging is enabled- Response:json
{ "logging-to-file": true }
- Response:
- PUT/PATCH
/logging-to-file— Enable or disable file logging- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":false}' \ http://localhost:8317/v0/management/logging-to-file - Response:json
{ "status": "ok" }
- Request:
Log Files
- GET
/logs— Stream recent log lines- Query params:
after(optional): Unix timestamp; only lines newer than this are returned.
- Response:json
{ "lines": ["2024-05-20 12:00:00 info request accepted"], "line-count": 125, "latest-timestamp": 1716206400 } - Notes:
- Requires file logging to be enabled; otherwise returns
{ "error": "logging to file disabled" }with400. - When no log file exists yet the response contains empty
linesandline-count: 0. latest-timestampis the largest parsed timestamp from this batch; if no timestamp is found it echoes the providedafter(or0), so clients can pass it back unchanged for incremental polling.line-countreflects the total number of lines scanned (including those filtered out byafter) and can be used to detect whether new log data arrived.
- Requires file logging to be enabled; otherwise returns
- Query params:
- DELETE
/logs— Remove rotated log files and truncate the active log- Response:json
{ "success": true, "message": "Logs cleared successfully", "removed": 3 }
- Response:
Request Error Logs
- GET
/request-error-logs— List error request log files when request logging is disabled- Response:json
{ "files": [ { "name": "error-2024-05-20.log", "size": 12345, "modified": 1716206400 } ] } - Notes:
- When
request-logis enabled, this endpoint always returns an empty list. - Files are discovered under the same log directory and must start with
error-and end with.log. modifiedis the last modification time as a Unix timestamp.
- When
- Response:
- GET
/request-error-logs/:name— Download a specific error request log- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -OJ 'http://localhost:8317/v0/management/request-error-logs/error-2024-05-20.log' - Notes:
namemust be a safe filename (no/or\) and match an existingerror-*.logentry; otherwise the server returns a validation or not-found error.- The handler performs a safety check to ensure the resolved path stays inside the log directory before streaming the file.
- Request:
Usage Statistics Toggle
- GET
/usage-statistics-enabled— Check whether telemetry collection is active- Response:json
{ "usage-statistics-enabled": true }
- Response:
- PUT/PATCH
/usage-statistics-enabled— Enable or disable collection- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":true}' \ http://localhost:8317/v0/management/usage-statistics-enabled - Response:json
{ "status": "ok" }
- Request:
Proxy Server URL
- GET
/proxy-url— Get the proxy URL string- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/proxy-url - Response:json
{ "proxy-url": "socks5://user:[email protected]:1080/" }
- Request:
- PUT/PATCH
/proxy-url— Set the proxy URL string- Request (PUT):bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":"socks5://user:[email protected]:1080/"}' \ http://localhost:8317/v0/management/proxy-url - Request (PATCH):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":"http://127.0.0.1:8080"}' \ http://localhost:8317/v0/management/proxy-url - Response:json
{ "status": "ok" }
- Request (PUT):
- DELETE
/proxy-url— Clear the proxy URL- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE http://localhost:8317/v0/management/proxy-url - Response:json
{ "status": "ok" }
- Request:
Quota Exceeded Behavior
- GET
/quota-exceeded/switch-project- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-project - Response:json
{ "switch-project": true }
- Request:
- PUT/PATCH
/quota-exceeded/switch-project— Boolean- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":false}' \ http://localhost:8317/v0/management/quota-exceeded/switch-project - Response:json
{ "status": "ok" }
- Request:
- GET
/quota-exceeded/switch-preview-model- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/quota-exceeded/switch-preview-model - Response:json
{ "switch-preview-model": true }
- Request:
- PUT/PATCH
/quota-exceeded/switch-preview-model— Boolean- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":true}' \ http://localhost:8317/v0/management/quota-exceeded/switch-preview-model - Response:json
{ "status": "ok" }
- Request:
API Keys (proxy service auth)
These endpoints update the inline config-api-key provider inside the auth.providers section of the configuration. Legacy top-level api-keys remain in sync automatically.
- GET
/api-keys— Return the full list- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/api-keys - Response:json
{ "api-keys": ["k1","k2","k3"] }
- Request:
- PUT
/api-keys— Replace the full list- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '["k1","k2","k3"]' \ http://localhost:8317/v0/management/api-keys - Response:json
{ "status": "ok" }
- Request:
- PATCH
/api-keys— Modify one item (old/neworindex/value)- Request (by old/new):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"old":"k2","new":"k2b"}' \ http://localhost:8317/v0/management/api-keys - Request (by index/value):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"index":0,"value":"k1b"}' \ http://localhost:8317/v0/management/api-keys - Response:json
{ "status": "ok" }
- Request (by old/new):
- DELETE
/api-keys— Delete one (?value=or?index=)- Request (by value):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?value=k1' - Request (by index):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/api-keys?index=0' - Response:json
{ "status": "ok" }
- Request (by value):
Gemini API Key
- GET
/gemini-api-key- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/gemini-api-key - Response:json
{ "gemini-api-key": [ {"api-key":"AIzaSy...01","base-url":"https://generativelanguage.googleapis.com","headers":{"X-Custom-Header":"custom-value"},"proxy-url":"","excluded-models":["gemini-1.5-pro","gemini-1.5-flash"]}, {"api-key":"AIzaSy...02","proxy-url":"socks5://proxy.example.com:1080","excluded-models":["gemini-pro-vision"]} ] }
- Request:
- PUT
/gemini-api-key- Request (array form):bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '[{"api-key":"AIzaSy-1","headers":{"X-Custom-Header":"vendor-value"},"excluded-models":["gemini-1.5-flash"]},{"api-key":"AIzaSy-2","base-url":"https://custom.example.com","excluded-models":["gemini-pro-vision"]}]' \ http://localhost:8317/v0/management/gemini-api-key - Response:json
{ "status": "ok" }
- Request (array form):
- PATCH
/gemini-api-key- Request (update by index):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"index":0,"value":{"api-key":"AIzaSy-1","base-url":"https://custom.example.com","headers":{"X-Custom-Header":"custom-value"},"proxy-url":"","excluded-models":["gemini-1.5-pro","gemini-pro-vision"]}}' \ http://localhost:8317/v0/management/gemini-api-key - Request (update by api-key match):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"match":"AIzaSy-1","value":{"api-key":"AIzaSy-1","headers":{"X-Custom-Header":"custom-value"},"proxy-url":"socks5://proxy.example.com:1080","excluded-models":["gemini-1.5-pro-latest"]}}' \ http://localhost:8317/v0/management/gemini-api-key - Response:json
{ "status": "ok" }
- Request (update by index):
- DELETE
/gemini-api-key- Request (by api-key):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE \ 'http://localhost:8317/v0/management/gemini-api-key?api-key=AIzaSy-1' - Request (by index):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE \ 'http://localhost:8317/v0/management/gemini-api-key?index=0' - Response:json
{ "status": "ok" } - Notes:
excluded-modelsis optional; the server lowercases, trims, deduplicates, and drops blank entries before saving.
- Request (by api-key):
Codex API KEY (object array)
- GET
/codex-api-key— List all- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/codex-api-key - Response:json
{ "codex-api-key": [ { "api-key": "sk-a", "base-url": "https://codex.example.com/v1", "proxy-url": "socks5://proxy.example.com:1080", "headers": { "X-Team": "cli" }, "excluded-models": ["gpt-4o-mini"] } ] }
- Request:
- PUT
/codex-api-key— Replace the list- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '[{"api-key":"sk-a","base-url":"https://codex.example.com/v1","proxy-url":"socks5://proxy.example.com:1080","headers":{"X-Team":"cli"},"excluded-models":["gpt-4o-mini","gpt-4.1-mini"]},{"api-key":"sk-b","base-url":"https://custom.example.com","proxy-url":"","headers":{"X-Env":"prod"},"excluded-models":["gpt-3.5-turbo"]}]' \ http://localhost:8317/v0/management/codex-api-key - Response:json
{ "status": "ok" }
- Request:
- PATCH
/codex-api-key— Modify one (byindexormatch)- Request (by index):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"index":1,"value":{"api-key":"sk-b2","base-url":"https://c.example.com","proxy-url":"","headers":{"X-Env":"stage"},"excluded-models":["gpt-3.5-turbo-instruct"]}}' \ http://localhost:8317/v0/management/codex-api-key - Request (by match):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"match":"sk-a","value":{"api-key":"sk-a","base-url":"https://codex.example.com/v1","proxy-url":"socks5://proxy.example.com:1080","headers":{"X-Team":"cli"},"excluded-models":["gpt-4o-mini","gpt-4.1"]}}' \ http://localhost:8317/v0/management/codex-api-key - Response:json
{ "status": "ok" }
- Request (by index):
- DELETE
/codex-api-key— Delete one (?api-key=or?index=)- Request (by api-key):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/codex-api-key?api-key=sk-b2' - Request (by index):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/codex-api-key?index=0' - Response:json
{ "status": "ok" } - Notes:
base-urlis required; submitting an emptybase-urlin PUT/PATCH removes the entry.headerslets you attach custom HTTP headers per key. Empty keys/values are stripped automatically.excluded-modelsaccepts model identifiers to block for this provider; the server lowercases, trims, deduplicates, and drops blank entries.
- Request (by api-key):
Request Retry Count
- GET
/request-retry— Get integer- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-retry - Response:json
{ "request-retry": 3 }
- Request:
- PUT/PATCH
/request-retry— Set integer- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":5}' \ http://localhost:8317/v0/management/request-retry - Response:json
{ "status": "ok" }
- Request:
Max Retry Interval
- GET
/max-retry-interval— Get the maximum retry interval in seconds- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/max-retry-interval - Response:json
{ "max-retry-interval": 30 }
- Request:
- PUT/PATCH
/max-retry-interval— Set the maximum retry interval in seconds- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":60}' \ http://localhost:8317/v0/management/max-retry-interval - Response:json
{ "status": "ok" }
- Request:
Request Log
- GET
/request-log— Get boolean- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/request-log - Response:json
{ "request-log": false }
- Request:
- PUT/PATCH
/request-log— Set boolean- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":true}' \ http://localhost:8317/v0/management/request-log - Response:json
{ "status": "ok" }
- Request:
WebSocket Authentication (ws-auth)
- GET
/ws-auth— Check whether the WebSocket gateway enforces authentication- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/ws-auth - Response:json
{ "ws-auth": true }
- Request:
- PUT/PATCH
/ws-auth— Enable or disable authentication for/ws/*endpoints- Request:bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"value":false}' \ http://localhost:8317/v0/management/ws-auth - Response:json
{ "status": "ok" } - Notes:
- When toggled from
false→true, the server terminates any existing WebSocket sessions so that reconnections must supply valid API credentials. - Disabling authentication leaves current sessions untouched but future connections will skip the auth middleware until re-enabled.
- When toggled from
- Request:
Claude API KEY (object array)
- GET
/claude-api-key— List all- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/claude-api-key - Response:json
{ "claude-api-key": [ { "api-key": "sk-a", "base-url": "https://example.com/api", "proxy-url": "socks5://proxy.example.com:1080", "headers": { "X-Workspace": "team-a" }, "excluded-models": ["claude-3-opus"] } ] }
- Request:
- PUT
/claude-api-key— Replace the list- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '[{"api-key":"sk-a","proxy-url":"socks5://proxy.example.com:1080","headers":{"X-Workspace":"team-a"},"excluded-models":["claude-3-opus"]},{"api-key":"sk-b","base-url":"https://c.example.com","proxy-url":"","headers":{"X-Env":"prod"},"excluded-models":["claude-3-sonnet","claude-3-5-haiku"]}]' \ http://localhost:8317/v0/management/claude-api-key - Response:json
{ "status": "ok" }
- Request:
- PATCH
/claude-api-key— Modify one (byindexormatch)- Request (by index):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"index":1,"value":{"api-key":"sk-b2","base-url":"https://c.example.com","proxy-url":"","headers":{"X-Env":"stage"},"excluded-models":["claude-3.7-sonnet"]}}' \ http://localhost:8317/v0/management/claude-api-key - Request (by match):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"match":"sk-a","value":{"api-key":"sk-a","base-url":"","proxy-url":"socks5://proxy.example.com:1080","headers":{"X-Workspace":"team-a"},"excluded-models":["claude-3-opus","claude-3.5-sonnet"]}}' \ http://localhost:8317/v0/management/claude-api-key - Response:json
{ "status": "ok" }
- Request (by index):
- DELETE
/claude-api-key— Delete one (?api-key=or?index=)- Request (by api-key):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?api-key=sk-b2' - Request (by index):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/claude-api-key?index=0' - Response:json
{ "status": "ok" } - Notes:
headersis optional; empty/blank pairs are removed automatically. To drop a header, simply omit it in your update payload.excluded-modelslets you block specific Claude models for a key; the server lowercases, trims, deduplicates, and removes blank entries.
- Request (by api-key):
OpenAI Compatibility Providers (object array)
- GET
/openai-compatibility— List all- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/openai-compatibility - Response:json
{ "openai-compatibility": [ { "name": "openrouter", "base-url": "https://openrouter.ai/api/v1", "api-key-entries": [ { "api-key": "sk", "proxy-url": "" } ], "models": [], "headers": { "X-Provider": "openrouter" } } ] }
- Request:
- PUT
/openai-compatibility— Replace the list- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '[{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk","proxy-url":""}],"models":[{"name":"m","alias":"a"}],"headers":{"X-Provider":"openrouter"}}]' \ http://localhost:8317/v0/management/openai-compatibility - Response:json
{ "status": "ok" }
- Request:
- PATCH
/openai-compatibility— Modify one (byindexorname)Request (by name):
bashcurl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"name":"openrouter","value":{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk","proxy-url":""}],"models":[],"headers":{"X-Provider":"openrouter"}}}' \ http://localhost:8317/v0/management/openai-compatibilityRequest (by index):
bashcurl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"index":0,"value":{"name":"openrouter","base-url":"https://openrouter.ai/api/v1","api-key-entries":[{"api-key":"sk","proxy-url":""}],"models":[],"headers":{"X-Provider":"openrouter"}}}' \ http://localhost:8317/v0/management/openai-compatibilityResponse:
json{ "status": "ok" }Notes:
- Legacy
api-keysinput remains accepted; keys are migrated intoapi-key-entriesautomatically so the legacy field will eventually remain empty in responses. headerslets you define provider-wide HTTP headers; blank keys/values are dropped.- Providers without a
base-urlare removed. Sending a PATCH withbase-urlset to an empty string deletes that provider.
- Legacy
- DELETE
/openai-compatibility— Delete (?name=or?index=)- Request (by name):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?name=openrouter' - Request (by index):bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/openai-compatibility?index=0' - Response:json
{ "status": "ok" }
- Request (by name):
OAuth Excluded Models
Configure per-provider model blocks for OAuth-based providers. Keys are provider identifiers, values are string arrays of model names to exclude.
- GET
/oauth-excluded-models— Get the current map- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/oauth-excluded-models - Response:json
{ "oauth-excluded-models": { "openai": ["gpt-4.1-mini"], "iflow": ["deepseek-v3.1", "glm-4.5"] } }
- Request:
- PUT
/oauth-excluded-models— Replace the full map- Request:bash
curl -X PUT -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"openai":["gpt-4.1-mini"],"iflow":["deepseek-v3.1","glm-4.5"]}' \ http://localhost:8317/v0/management/oauth-excluded-models - Response:json
{ "status": "ok" } - Notes:
- The body can also be wrapped as
{ "items": { ... } }; in both cases empty/blank model names are trimmed out.
- The body can also be wrapped as
- Request:
- PATCH
/oauth-excluded-models— Upsert or delete a single provider entry- Request (upsert):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"provider":"iflow","models":["deepseek-v3.1","glm-4.5"]}' \ http://localhost:8317/v0/management/oauth-excluded-models - Request (delete provider by sending empty models):bash
curl -X PATCH -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"provider":"iflow","models":[]}' \ http://localhost:8317/v0/management/oauth-excluded-models - Response:json
{ "status": "ok" } - Notes:
provideris normalized to lowercase. Sending an emptymodelslist removes that provider; if the provider does not exist, a404is returned.
- Request (upsert):
- DELETE
/oauth-excluded-models— Delete all models for a provider- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -X DELETE 'http://localhost:8317/v0/management/oauth-excluded-models?provider=iflow' - Response:json
{ "status": "ok" }
- Request:
Auth File Management
Manage JSON token files under auth-dir: list, download, upload, delete.
GET
/auth-files— List- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' http://localhost:8317/v0/management/auth-files - Response (when the runtime auth manager is available):json
{ "files": [ { "id": "[email protected]", "name": "[email protected]", "provider": "claude", "label": "Claude Prod", "status": "ready", "status_message": "ok", "disabled": false, "unavailable": false, "runtime_only": false, "source": "file", "path": "/abs/path/auths/[email protected]", "size": 2345, "modtime": "2025-08-30T12:34:56Z", "email": "[email protected]", "account_type": "anthropic", "account": "workspace-1", "created_at": "2025-08-30T12:00:00Z", "updated_at": "2025-08-31T01:23:45Z", "last_refresh": "2025-08-31T01:23:45Z" } ] } - Notes:
- Entries are sorted case-insensitively by
name.status,status_message,disabled, andunavailablemirror the runtime auth manager so you can see whether a credential is healthy. runtime_only: trueindicates the credential only exists in memory (for example Git/Postgres/ObjectStore backends);sourceswitches tomemory. When a.jsonfile exists on disk,source=fileand the response includespath/size/modtime.email,account_type,account, andlast_refreshare pulled from the JSON metadata (keys such aslast_refresh,lastRefreshedAt,last_refreshed_at, etc.).- If the runtime auth manager is unavailable the handler falls back to scanning
auth-dir, returning onlyname,size,modtime,type, andemail. runtime_onlyentries cannot be downloaded or deleted via the file endpoints—they must be revoked from the upstream provider or a different API.
- Entries are sorted case-insensitively by
- Request:
GET
/auth-files/download?name=<file.json>— Download a single file- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -OJ 'http://localhost:8317/v0/management/auth-files/download?name=acc1.json' - Notes:
namemust be a.jsonfilename. Onlysource=fileentries have a backing file to export;runtime_onlycredentials cannot be downloaded.
- Request:
POST
/auth-files— Upload- Request (multipart):bash
curl -X POST -F 'file=@/path/to/acc1.json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/auth-files - Request (raw JSON):bash
curl -X POST -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d @/path/to/acc1.json \ 'http://localhost:8317/v0/management/auth-files?name=acc1.json' - Response:json
{ "status": "ok" } - Notes:
- The core auth manager must be active; otherwise the API returns
503with{ "error": "core auth manager unavailable" }. - Both multipart and raw JSON uploads must use filenames ending in
.json; upon success the credential is registered with the runtime auth manager immediately.
- The core auth manager must be active; otherwise the API returns
- Request (multipart):
DELETE
/auth-files?name=<file.json>— Delete a single file- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?name=acc1.json' - Response:json
{ "status": "ok" } - Notes:
- Only on-disk
.jsonfiles are removed; after a successful deletion the runtime manager is instructed to disable the corresponding credential.runtime_onlyentries are unaffected.
- Only on-disk
- Request:
DELETE
/auth-files?all=true— Delete all.jsonfiles underauth-dir- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' -X DELETE 'http://localhost:8317/v0/management/auth-files?all=true' - Response:json
{ "status": "ok", "deleted": 3 } - Notes:
- Only files on disk are counted and removed; each successful deletion also triggers a disable call into the runtime auth manager. Purely in-memory entries stay untouched.
- Request:
Vertex Credential Import
Mirrors the CLI vertex-import helper and stores Google service account JSON as vertex-<project>.json files inside auth-dir.
- POST
/vertex/import— Upload a Vertex service account key- Request (multipart):bash
curl -X POST \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -F 'file=@/path/to/my-project-sa.json' \ -F 'location=us-central1' \ http://localhost:8317/v0/management/vertex/import - Response:json
{ "status": "ok", "auth-file": "/abs/path/auths/vertex-my-project.json", "project_id": "my-project", "email": "[email protected]", "location": "us-central1" } - Notes:
- Uploads must be sent as
multipart/form-datausing thefilefield. The payload is validated andprivate_keyis normalized; malformed JSON or missingproject_idyields400. - The optional
locationform (or query) field overrides the defaultus-central1region recorded in the credential metadata. - The handler persists the credential via the same token store as other auth uploads; failures return
500with{ "error": "save_failed", ... }.
- Uploads must be sent as
- Request (multipart):
Login/OAuth URLs
These endpoints initiate provider login flows and return a URL to open in a browser. Tokens are saved under auths/ once the flow completes.
For Anthropic, Codex, Gemini CLI, Antigravity, and iFlow you can append ?is_webui=true to reuse the embedded callback forwarder when launching from the management UI.
GET
/anthropic-auth-url— Start Anthropic (Claude) login- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/anthropic-auth-url - Response:json
{ "status": "ok", "url": "https://...", "state": "anth-1716206400" } - Notes:
- Add
?is_webui=truewhen triggering from the built-in UI to reuse the local callback service.
- Add
- Request:
GET
/codex-auth-url— Start Codex login- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/codex-auth-url - Response:json
{ "status": "ok", "url": "https://...", "state": "codex-1716206400" }
- Request:
GET
/gemini-cli-auth-url— Start Google (Gemini CLI) login- Query params:
project_id(optional): Google Cloud project ID.
- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ 'http://localhost:8317/v0/management/gemini-cli-auth-url?project_id=<PROJECT_ID>' - Response:json
{ "status": "ok", "url": "https://...", "state": "gem-1716206400" } - Notes:
- When
project_idis omitted, the server queries Cloud Resource Manager for accessible projects, picks the first available one, and stores it in the token file (marked withauto: true). - The flow checks and, if needed, enables
cloudaicompanion.googleapis.comvia the Service Usage API; failures surface through/get-auth-statusas errors such asproject activation required: ....
- When
- Query params:
GET
/antigravity-auth-url— Start Antigravity login- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/antigravity-auth-url - Response:json
{ "status": "ok", "url": "https://...", "state": "ant-1716206400" } - Notes:
- Add
?is_webui=truewhen triggering from the built-in UI so the server starts a temporary local callback forwarder on port51121and reuses the main HTTP port for the final redirect.
- Add
- Request:
GET
/qwen-auth-url— Start Qwen login (device flow)- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/qwen-auth-url - Response:json
{ "status": "ok", "url": "https://...", "state": "gem-1716206400" }
- Request:
GET
/iflow-auth-url— Start iFlow login- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ http://localhost:8317/v0/management/iflow-auth-url - Response:json
{ "status": "ok", "url": "https://...", "state": "ifl-1716206400" }
- Request:
POST
/iflow-auth-url— Authenticate using an existing iFlow cookie- Request body:bash
curl -X POST -H 'Content-Type: application/json' \ -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ -d '{"cookie":"<YOUR_IFLOW_COOKIE>"}' \ http://localhost:8317/v0/management/iflow-auth-url - Successful response:json
{ "status": "ok", "saved_path": "/abs/path/auths/iflow-user.json", "email": "[email protected]", "expired": "2025-05-20T10:00:00Z", "type": "cookie" } - Notes:
- The
cookiefield is required and must be non-empty; invalid or malformed cookies return400with{ "status": "error", "error": "..." }. - On success the server normalizes the cookie, exchanges it for an API token, persists it as an
iflow-*.jsonauth file, and returns the saved path and basic metadata.
- The
- Request body:
GET
/get-auth-status?state=<state>— Poll OAuth flow status- Request:bash
curl -H 'Authorization: Bearer <MANAGEMENT_KEY>' \ 'http://localhost:8317/v0/management/get-auth-status?state=<STATE_FROM_AUTH_URL>' - Response examples:json
{ "status": "wait" }json{ "status": "ok" }json{ "status": "error", "error": "Authentication failed" } - Notes:
- The
statequery parameter must match the value returned by the login endpoint. Once a flow reachesstatus: "ok"orstatus: "error", the server deletes the state; subsequent polls receive{ "status": "ok" }to signal completion. status: "wait"indicates the flow is still waiting for a callback or token exchange—continue polling as needed.
- The
- Request:
Error Responses
Generic error format:
- 400 Bad Request:
{ "error": "invalid body" } - 401 Unauthorized:
{ "error": "missing management key" }or{ "error": "invalid management key" } - 403 Forbidden:
{ "error": "remote management disabled" } - 404 Not Found:
{ "error": "item not found" }or{ "error": "file not found" } - 422 Unprocessable Entity:
{ "error": "invalid_config", "message": "..." } - 500 Internal Server Error:
{ "error": "failed to save config: ..." } - 503 Service Unavailable:
{ "error": "core auth manager unavailable" }
Notes
- Changes are written back to the YAML config file and hot‑reloaded by the file watcher and clients.
allow-remote-managementandremote-management-keycannot be changed via the API; configure them in the config file.