API Reference
Every route is mounted under /api. JSON unless otherwise noted; SSE
endpoints emit text/event-stream; chat turns use a WebSocket
described in Chat (WebSocket).
Interactive docs (Scalar) live at /api/docs when
enable_api_docs=true (or OCTOP_ENABLE_API_DOCS=1). The schema
is at /api/openapi.json.
Authentication
| Header |
Value |
Authorization |
Bearer <access_token> from POST /api/auth/login |
Tokens expire after OCTOP_ACCESS_TOKEN_TTL seconds (default 24 h).
Rotating the JWT secret (octop admin rotate-jwt-secret) invalidates
every outstanding token immediately. Login attempts are rate-limited
(OCTOP_LOGIN_MAX_ATTEMPTS / OCTOP_LOGIN_LOCKOUT_SECONDS); the
admin can clear the lockout with POST /api/users/{id}/unlock-login.
Auth column legend
- public — no token required.
- user — any logged-in account.
- owner — same user that owns the resource (or admin).
- admin — admin role required.
Public endpoints (no token)
/api/docs, /api/openapi.json, /api/health, /api/setup/*,
/api/auth/login, /api/auth/captcha, /api/auth/oidc/status, /api/auth/oidc/start,
/api/auth/oidc/callback, /api/auth/oidc/exchange, /api/auth/oauth/status,
/api/auth/oauth/start, /api/auth/oauth/callback, /api/auth/oauth/exchange,
/api/connectors/oauth/callback, and /api/internal/mcp/*. All other routes are JWT-gated by
api/middleware/jwt_auth.py; the setup lockdown middleware
(api/middleware/setup_lockdown.py) additionally blocks non-setup
routes until the wizard finishes.
Password login may require a vendor captcha token. GET /api/auth/captcha
returns {provider: "slider"} by default (dashboard slider only; no server
check). When a strong provider is active (turnstile, hcaptcha,
recaptcha, recaptcha-v3, tencent), POST /api/auth/login must include
captcha_token (max 4096 characters; for tencent the dashboard sends the
callback pair as ticket:randstr, verified via GET with the client IP).
Captcha failures do not increment
login lockout. OIDC login is unchanged. A request for an unknown username
with a garbage token still triggers one outbound siteverify request (bounded
by a 10s timeout and the token length limit); a known locked user is
rejected before siteverify.
Offline recovery: delete the captcha.settings row via local CLI /
settings_repo if a settings-sourced strong provider is unreachable. If
the process refused to start because OCTOP_CAPTCHA_PROVIDER is strong
without both keys and no readable settings blob, edit ~/.octop/env on
disk — the dashboard and PUT /api/envs are unreachable until the
process starts. GET /api/envs redacts OCTOP_CAPTCHA_SECRET as
********; sending that sentinel on PUT keeps the on-disk value.
If you serve the dashboard behind a CSP, allow
challenges.cloudflare.com; js.hcaptcha.com, newassets.hcaptcha.com,
api.hcaptcha.com; www.google.com, www.gstatic.com;
turing.captcha.qcloud.com, captcha.qq.com, ssl.captcha.qq.com. Octop
does not set these headers itself.
Setup & auth
| Method |
Path |
Auth |
Notes |
GET |
/setup/status |
public |
{required, completed, has_admin} |
GET |
/setup/presets |
public |
Provider templates for the wizard |
POST |
/setup/begin |
public |
Begin a wizard session (no password) |
POST |
/setup/verify-password |
public |
Exchange the wizard password for a session token |
GET |
/setup/validate-token |
public |
Check a wizard session token |
POST |
/setup/initial-admin |
public |
body {username, password, display_name?, email?} → 201 |
POST |
/setup/resume-wizard |
public |
Issue a fresh wizard token mid-setup |
POST |
/setup/test-provider |
public |
Ping a provider draft (kind/base_url/api_key/model) |
POST |
/setup/finish |
public |
Finalise setup and unlock the rest of the API |
GET |
/auth/captcha |
public |
{provider, site_key?} for the login widget; 503 while setup is required |
POST |
/auth/login |
public |
body {username, password, captcha_token?} (username may be email) → {access_token, role, user, ...} |
GET |
/auth/oidc/status |
public |
OIDC login availability and provider display name |
POST |
/auth/oidc/start |
public |
body {redirect_after?} → identity-provider authorization URL |
GET |
/auth/oidc/callback |
public |
Identity-provider callback; redirects to dashboard login completion |
POST |
/auth/oidc/exchange |
public |
body {code} → same JWT response as /auth/login |
GET |
/auth/oidc/config |
admin |
OIDC provider configuration and callback URL; client secret is omitted |
PUT |
/auth/oidc/config |
admin |
Write OIDC provider configuration; client_secret is write-only |
POST |
/auth/oidc/config/test |
admin |
Verify configured discovery metadata and JWKS endpoint |
GET |
/auth/oauth/status |
public |
{providers:[{kind, display_name, enabled}]} |
POST |
/auth/oauth/start |
public |
body {kind, redirect_after?} → authorization URL |
GET |
/auth/oauth/callback |
public |
App OAuth callback (code or DingTalk authCode); same completion page as OIDC |
POST |
/auth/oauth/exchange |
public |
Same one-time code exchange as /auth/oidc/exchange |
POST |
/auth/oauth/bind/start |
user |
body {kind, redirect_after?} → bind the identity to the current user |
POST |
/auth/oauth/unbind |
user |
Unlink one SSO identity by kind (requires a local password when it is the last login method) |
GET |
/auth/oauth/providers/{kind} |
admin |
Provider config; kind is oidc, feishu, dingtalk, or wecom |
PUT |
/auth/oauth/providers/{kind} |
admin |
Upsert provider config; client_secret is write-only; WeCom uses extra.agent_id |
POST |
/auth/oauth/providers/{kind}/test |
admin |
Test provider credentials |
POST |
/auth/logout |
user |
204 |
GET |
/auth/me |
user |
{id, username, role, display_name, locale, ...} |
PATCH |
/auth/me |
user |
body {display_name?, locale?, ...} |
POST |
/auth/change-password |
user |
body {old_password, new_password} → 204 |
GET |
/health |
public |
{status: "ok", version} |
Users (admin)
| Method |
Path |
Auth |
Notes |
GET |
/users |
admin |
[{id, username, role, display_name, email, enabled, ...}] |
POST |
/users |
admin |
body {username, password, role, display_name?, email?, permissions?, workspace_root_dir?, token_quota?} → 201 |
GET |
/users/{id} |
admin |
full user row |
PATCH |
/users/{id} |
admin |
body subset of {role, display_name, email, enabled, locale} |
POST |
/users/{id}/reset-password |
admin |
body {new_password} → 204 |
POST |
/users/{id}/unlock-login |
admin |
204 (clears the lockout) |
DELETE |
/users/{id} |
admin |
204 |
Agents
| Method |
Path |
Auth |
Notes |
GET |
/agents |
user |
[{id, agent_id, name, persona_mbti, state, unread_count, ...}] |
POST |
/agents |
user |
body {name, persona_mbti?, default_model?, system_prompt?, description?, icon?, template_name?, config?} → 201 |
GET |
/agents/{id} |
owner |
full agent row |
PATCH |
/agents/{id} |
owner |
body subset of create body |
DELETE |
/agents/{id} |
owner |
204 |
POST |
/agents/{id}/start |
owner |
204 |
POST |
/agents/{id}/stop |
owner |
204 |
POST |
/agents/{id}/reload |
owner |
204 (rebuild harness runtime) |
POST |
/agents/{id}/read |
owner |
204 (mark unread badge cleared) |
GET |
/agents/{id}/status |
owner |
{state, last_error?, memory_maintenance?, ...} |
POST |
/agents/from-expert/{expert_id} |
user |
body {name, ...} → 201 (creates from bundled expert template) |
GET |
/agents/{id}/tool-settings |
owner |
built-in + installed plugin tools with enable / disableable / available flags |
PUT |
/agents/{id}/tool-settings |
owner |
body {disabled_builtin: string[], plugins?} — persists denylist + plugin flags (hot-sync, no reload) |
PATCH |
/agents/{id}/tool-settings/{tool_name} |
owner |
body {enabled, source, plugin_id?} — toggle one tool (hot-sync) |
Skills and skill packages
Skill copy operations create snapshots; they do not keep the source and
destination synchronized after the request completes.
| Method |
Path |
Auth |
Notes |
GET |
/skill-packages?writable_only=true |
user (skill_packages) |
List only packages the current user may modify; admins may modify all packages |
POST |
/agents/{id}/skill-packages/{package_id}/copy |
owner (skill_packages) |
body {skill_slugs: string[], overwrite?: boolean}; copy selected package skills into the workspace |
POST |
/agents/{id}/skills/{slug}/push-to-package |
owner (skill_packages) |
body {package_id, overwrite?: boolean}; copy a workspace skill into a package created by the user (or any package for admins) |
Chat (WebSocket)
| Path |
Auth |
Notes |
WS /agents/{id}/chat/ws?token=<jwt> |
owner |
Primary dashboard turn endpoint. Send {"type":"user_turn", ...} frames (optional hitl_policy is a thread-scoped tool-approval bypass); server replies with harness stream chunks ending in {"type":"done"} or {"type":"error","message":"..."}. {"type":"ping"} → {"type":"pong"}. {"type":"subscribe","thread_id"} → {"type":"turn_status","thread_id","active"} (attach to an in-flight turn without cancelling on disconnect). {"type":"cancel","thread_id"} stops the active turn (explicit stop; disconnect alone does not cancel). |
GET /agents/{id}/chat/welcome |
agent access |
{welcome_message, quick_prompts, task_examples}; task_examples is null when the workspace field is absent |
POST /agents/{id}/chat/polish |
owner |
body {text, default_model?} → {text} (one-shot prompt refinement) |
POST /agents/{id}/chat/hitl/resume |
owner |
body {thread_id, decisions: [...], hitl_policy?} → SSE chunk stream; finishes with {"type":"done"}. Optional hitl_policy (ask / allow_all / allow_tools) is a thread-scoped bypass and does not change global tool-approval settings. |
Legacy SSE
The previous POST /agents/{id}/chat/stream is gone. The dashboard
streams turns over the WebSocket above; HITL resume stays on SSE
because each request is a one-shot continuation.
Threads & history
| Method |
Path |
Auth |
Notes |
GET |
/agents/{id}/chat/sessions |
owner |
[{id, thread_id, title, archived, last_active, unread, ...}] |
POST |
/agents/{id}/chat/sessions |
owner |
body {session_key?} → {thread_id, session_key} |
PATCH |
/agents/{id}/chat/sessions/{thread_id} |
owner |
body {title?, pinned?} → updated row |
DELETE |
/agents/{id}/chat/sessions/{thread_id} |
owner |
204 (archives the active row) |
GET |
/agents/{id}/chat/sessions/{thread_id}/history |
owner |
paginated message history; turn_active tells a reconnecting client whether to re-subscribe over the chat WebSocket |
Tool result blocks. tool_result blocks in history (and tool_result
frames on the chat WebSocket) carry the tool's return value in output. When a
plugin returns a large octop_ui payload, the backend offloads the envelope's
data field: output then contains the slim envelope with
data_ref: "artifact", and the full payload is on the block's artifact key
(absent otherwise). An explicit data key always takes precedence over
data_ref when both appear. Clients that render plugin UIs must resolve
data_ref from artifact; clients that only read output keep working.
Trajectory ledger
| Method |
Path |
Auth |
Notes |
GET |
/agents/{id}/threads/{thread_id}/trajectory |
owner |
paginated event summaries; use before_seq for older rows |
GET |
/agents/{id}/threads/{thread_id}/trajectory/events/{event_id} |
owner |
full event payload |
GET |
/agents/{id}/threads/{thread_id}/trajectory/metrics |
owner |
aggregated turn, timing, and token metrics |
GET |
/agents/{id}/threads/{thread_id}/trajectory/stream |
owner |
live SSE events; resume with after_seq or Last-Event-ID |
GET |
/agents/{id}/threads/{thread_id}/trajectory/export |
owner |
full ledger download as JSONL (default) or JSON |
Channels
| Method |
Path |
Auth |
Notes |
GET |
/agents/{aid}/channels |
owner |
list of channel rows |
POST |
/agents/{aid}/channels |
owner |
body {kind, name, config} → 201 |
GET |
/agents/{aid}/channels/{cid} |
owner |
channel row |
PATCH |
/agents/{aid}/channels/{cid} |
owner |
body subset → updated row |
DELETE |
/agents/{aid}/channels/{cid} |
owner |
204 |
POST |
/agents/{aid}/channels/{cid}/test |
owner |
{ok, error?} (instantiate → start → stop) |
POST |
/agents/{aid}/channels/probe |
owner |
{ok, reason?, detail?} — preflight a candidate config |
POST |
/agents/{aid}/channels/{platform}/qrcode/generate |
owner |
platform-specific bot creator (wecom, weixin, feishu, yuanbao) |
POST |
/agents/{aid}/channels/{platform}/qrcode/poll |
owner |
poll bot creator state |
POST |
/agents/{aid}/channels/{platform}/bot-creator/start |
owner |
start a bot-creator flow |
POST |
/agents/{aid}/channels/{platform}/bot-creator/poll |
owner |
poll progress |
POST |
/agents/{aid}/channels/{platform}/bot-creator/stop |
owner |
stop an in-flight bot creator |
Cron
| Method |
Path |
Auth |
Notes |
GET |
/settings/timezone |
user |
process-level {timezone} from default_timezone |
GET |
/settings/upload |
user |
{max_upload_mb, max_upload_bytes} from max_upload_mb |
GET |
/settings/captcha |
captcha |
{active, available, providers, source, v3_min_score}; secrets omitted |
PUT |
/settings/captcha |
captcha |
merge {active?, providers?}; empty secret keeps ciphertext; null removes a pair |
GET |
/cron/settings |
user |
compat alias of /settings/timezone |
GET |
/agents/{aid}/cron/examples |
agent access |
{task_examples} from workspace .octop/manifest.json ({zh,en} string arrays, display-normalized to 3 or 6); null if the field is absent (dashboard keeps default cards). Prefer GET /agents/{aid}/chat/welcome which includes the same field. |
GET |
/agents/{aid}/cron |
owner only |
list cron rows; non-owners (including admin) get [] |
POST |
/agents/{aid}/cron |
owner only |
body {name?, trigger, prompt, session_key?, fresh_thread?, enabled?, model?, task_type?} → 201 |
GET |
/agents/{aid}/cron/{cid} |
owner only |
cron row |
PATCH |
/agents/{aid}/cron/{cid} |
owner only |
body subset → updated row |
DELETE |
/agents/{aid}/cron/{cid} |
owner only |
204 |
POST |
/agents/{aid}/cron/{cid}/run-now |
owner only |
204 (fire immediately, off-schedule) |
task_type is "text" (push prompt directly to the session) or
"agent" (run the prompt through the LLM and push the reply).
Default: "agent". trigger accepts cron expressions
("0 9 * * *") plus the interval:N / date:ISO8601 aliases
documented in infra/cron/trigger.py. prompt must be non-empty and
≤ 2000 characters. name is an optional display label; when omitted,
the server derives one from prompt.
Providers
| Method |
Path |
Auth |
Notes |
GET |
/providers |
user |
providers visible to the user (own + shared) |
POST |
/providers |
user |
body {name, kind, base_url?, api_key?, model?, ...} → 201 |
PATCH |
/providers/{id} |
owner |
body subset → updated row |
DELETE |
/providers/{id} |
owner |
204 (refuses if any agent references it) |
POST |
/providers/{id}/test |
user |
{ok, latency_ms?, error?} (one-token ping with 10 s timeout) |
POST |
/admin/providers |
admin |
same body as user POST; row has user_id = NULL |
PATCH |
/admin/providers/{id} |
admin |
as user PATCH but works on shared rows |
DELETE |
/admin/providers/{id} |
admin |
204 |
Models
| Method |
Path |
Auth |
Notes |
GET |
/models/presets |
user |
provider templates from octop-harness |
GET |
/models |
user |
resolved models across enabled providers |
GET |
/models/active |
user |
{provider_name, model} |
PUT |
/models/active |
admin |
body {provider_name, model} |
Media generation models
These instance-wide endpoints require the providers permission (administrators bypass
permission checks). The Ark API key is write-only and encrypted at rest. Saving settings
reloads running agents so the image and video tools receive the new configuration.
| Method |
Path |
Auth |
Notes |
GET |
/admin/media-generation |
providers |
Return Volcengine Ark image/video settings; never returns the API key |
PUT |
/admin/media-generation |
providers |
Save enabled tools and Seedream/Seedance model IDs; an included API key is verified before saving |
POST |
/admin/media-generation/test |
providers |
Test credentials or a selected image/video model; model tests submit real, potentially billable requests |
Voice
| Method |
Path |
Auth |
Notes |
GET |
/voice/presets |
user |
voice provider presets |
GET |
/voice/providers |
user |
user's voice providers |
GET |
/voice/active |
user |
active TTS / STT configuration |
PUT |
/voice/active |
user |
update active voice configuration |
POST |
/voice/stt |
user |
body {audio, format?, language?} → {text, segments?} |
POST |
/voice/tts |
user |
body {text, voice?, format?} → audio bytes |
GET/POST/PATCH/DELETE |
/admin/voice/providers |
admin |
admin voice provider CRUD |
MBTI & personas
| Method |
Path |
Auth |
Notes |
GET |
/mbti/codes |
user |
[{code: "INTJ", ...}, ...] (16 codes + _default) |
GET |
/mbti/codes/{code} |
user |
full profile (dimensions, behaviour, UI metadata) |
GET |
/mbti/preview/{code} |
user |
rendered persona template (legacy /api/personas/{code}) |
PUT |
/agents/{aid}/mbti |
owner |
body {code} → apply persona and reload |
GET |
/personas |
user |
[{code}, ...] (compat shim) |
GET |
/personas/{code} |
user |
rendered template (compat shim) |
Persona content lives in src/octop/infra/agents/persona/mbti_profiles.py —
see Personas.
Experts
| Method |
Path |
Auth |
Notes |
GET |
/experts |
user |
bundled expert catalog (includes task_examples {zh,en} when present) |
GET |
/experts/{expert_id} |
user |
full expert template (SOUL.md, skills, files, task_examples) |
POST |
/agents/from-expert/{expert_id} |
user |
body {name, locale?, quick_prompts?, ...} → 201; optional quick_prompts overwrites workspace cards after seed |
Bundled experts live in src/octop/infra/agents/experts/library/
(en/zh divisions); the catalog is locale-aware via
Accept-Language / user preference.
Workspace, skills, subagents, memory, files
| Method |
Path |
Auth |
Notes |
GET |
/agents/{aid}/workspace/tree |
owner |
list dir (default .) |
GET |
/agents/{aid}/workspace/file |
owner |
read a content file |
PUT |
/agents/{aid}/workspace/file |
owner |
write a content file (via BackendWorkspace) |
DELETE |
/agents/{aid}/workspace/file |
owner |
delete a content file |
POST |
/agents/{aid}/workspace/rename |
owner |
rename / move |
POST |
/agents/{aid}/workspace/upload |
owner |
multipart upload → backend (not max_upload_mb) |
GET |
/agents/{aid}/workspace/download |
owner |
download a file |
GET |
/agents/{aid}/workspace/glob |
owner |
glob backend paths |
GET |
/agents/{aid}/workspace/grep |
owner |
grep backend files |
GET |
/agents/{aid}/workspace/... |
owner |
see api/routers/workspace.py |
GET |
/agents/{aid}/skills |
owner |
list installed skills |
PUT |
/agents/{aid}/skills/{slug} |
owner |
enable / disable a skill |
GET |
/agents/{aid}/skills/hub/search |
user |
Skill Hub search |
GET |
/agents/{aid}/skills/hub/rankings |
user |
Skill Hub rankings |
POST |
/agents/{aid}/skills/hub/install |
owner |
body {slug, version?} → 201 |
GET |
/subagent-catalog/divisions |
user |
bundled subagent divisions |
GET |
/subagent-catalog |
user |
bundled subagent catalog |
GET |
/subagent-catalog/{slug} |
user |
full subagent definition |
GET |
/agents/{aid}/subagents |
owner |
installed subagents for an agent |
POST |
/agents/{aid}/subagents |
owner |
install a bundled subagent |
GET |
/agents/{aid}/heartbeat-config |
owner |
read heartbeat YAML |
PUT |
/agents/{aid}/heartbeat-config |
owner |
write heartbeat YAML |
GET |
/agents/{aid}/memory/daily |
owner |
list daily memory files |
GET |
/agents/{aid}/memory/daily/{filename} |
owner |
read one daily memory |
DELETE |
/agents/{aid}/memory/daily/{filename} |
owner |
delete one daily memory |
GET/POST |
/memory/... |
user |
memory API (dashboard memory tab) |
ACP (Agent Client Protocol)
| Method |
Path |
Auth |
Notes |
GET |
/acp |
user |
current user's global runner list |
PUT |
/acp |
user |
replace global runners |
GET |
/acp/{runner_name} |
user |
one runner |
PUT |
/acp/{runner_name} |
user |
upsert one runner |
DELETE |
/acp/{runner_name} |
user |
delete a custom runner (built-ins are protected) |
GET |
/agents/{aid}/acp |
owner |
global runners + this agent's tool_enabled flag |
PUT |
/agents/{aid}/acp |
owner |
update tool_enabled and optionally the global list |
PUT |
/agents/{aid}/acp/tool |
owner |
toggle the acp_runner tool only |
See ACP integration for the runner object schema and the
Zed setup example.
Storage backends
| Method |
Path |
Auth |
Notes |
GET/POST/PATCH/DELETE |
/storage-backends |
user |
per-user remote backend connections |
GET/POST/PATCH/DELETE |
/admin/storage-backends |
admin |
admin-managed backends |
Host filesystem (dashboard)
Browse the host OS directory tree when configuring a local backend
root_dir (local_shell / filesystem). Authenticated users only;
sensitive mounts (/proc, /sys, /dev, /etc, /root on POSIX)
are rejected, except the process home and its subdirectories (so a
server running as root may use /root as the default root_dir).
Listing is single-level and capped; write probe runs only
for non-/ paths.
| Method |
Path |
Auth |
Notes |
GET |
/filesystem/dirs?path=<abs> |
user |
{path, entries: [{path, name}]} — one directory level |
POST |
/filesystem/probe |
user |
body {path} → {ok, path?} or {ok: false, code, detail?} (not_directory, permission_denied, write_failed, not_allowed) |
POST |
/filesystem/mkdir |
user |
body {path, base_name?} → {path, name} — create child dir (base_name defaults to New Folder; collisions become Name (2), …) |
POST |
/filesystem/rename |
user |
body {path, new_name} → {path, name} — rename basename only |
Connectors & OAuth
| Method |
Path |
Auth |
Notes |
GET |
/connectors/catalog |
user |
connector catalog (Notion, Figma, …) |
GET |
/connectors/test-credentials |
user |
preflight credentials |
GET |
/connector-instances |
user |
list instances |
POST |
/connector-instances |
user |
create instance |
GET/PATCH/DELETE |
/connector-instances/{id} |
user |
CRUD on an instance |
POST |
/connector-instances/{id}/test |
user |
test a configured instance |
POST |
/connector-instances/{id}/refresh |
user |
refresh OAuth tokens |
GET |
/connectors/auth/{kind}/info |
user |
auth flow info |
GET |
/connectors/auth/{kind}/authorize-url |
user |
build the authorize URL |
POST |
/connectors/auth/{kind}/exchange-code |
user |
exchange auth code |
POST |
/connectors/oauth/start |
user |
start OAuth (catalog or custom MCP via target) |
POST |
/connectors/oauth/{kind}/start |
user |
legacy catalog OAuth start |
PUT |
/connectors/custom-mcp |
user |
save custom MCP server map |
PATCH |
/connectors/custom-mcp/servers/{name} |
user |
patch enabled / default_open on one server |
POST |
/connectors/custom-mcp/test |
user |
probe a custom MCP server (inline spec or saved name) |
GET |
/connectors/oauth/callback |
public |
OAuth redirect target |
GET |
/connectors/oauth/pending/{state_id} |
user |
poll the OAuth result |
Custom MCP OAuth (streamable HTTP, public HTTPS URL only): Octop discovers the authorization
server from the MCP URL (401 / RFC 9728 protected-resource metadata), requires dynamic client
registration (DCR), stores encrypted tokens in the custom MCP spec, and injects Authorization: Bearer when loading tools. Loopback and LAN MCP URLs may use HTTP and do not use remote OAuth
discovery.
Internal MCP (octop-harness agents)
| Method |
Path |
Auth |
Notes |
POST/GET/… |
/internal/mcp/* |
public (mTLS / network-isolated) |
MCP gateway used by octop-harness agents (not the dashboard) |
Observability & security
| Method |
Path |
Auth |
Notes |
GET/PUT |
/admin/observability |
admin |
Langfuse configuration (host, project, env) |
GET |
/admin/security |
admin |
global security policy |
PUT |
/admin/security |
admin |
update global policy |
GET |
/admin/security/tool-guard/rules |
admin |
active command guard rules |
GET |
/admin/security/tool-guard/rules/raw |
admin |
editable YAML |
PUT |
/admin/security/tool-guard/rules/raw |
admin |
save YAML |
POST |
/admin/security/tool-guard/rules/reset |
admin |
reset to shipped defaults |
GET |
/admin/security/defaults |
admin |
defaults + rule catalogs |
TLS (Let's Encrypt)
| Method |
Path |
Auth |
Notes |
GET |
/admin/tls/status |
admin |
current cert + issuance task status |
POST |
/admin/tls/preflight |
admin |
preflight (port 80, DNS) |
POST |
/admin/tls/issue |
admin |
start HTTP-01 issuance |
GET /.well-known/acme-challenge/{token} is the HTTP-01 challenge
endpoint (public, mounted directly in api/app.py).
Browser, terminal, uploads
| Method |
Path |
Auth |
Notes |
WS/POST/GET/… |
/agents/{aid}/terminal |
owner |
AI-assisted remote PTY |
GET |
/agents/{aid}/terminal/context |
owner |
recent terminal context for the AI helper |
WS/POST/GET/… |
/browser/... |
user |
octop-browser sessions, live stream, record/replay |
POST |
/browser/shutdown |
user |
stop the current user's Octop-managed Chrome |
POST |
/agents/{aid}/upload |
user |
multipart upload → {workspace}/inbound/ |
POST |
/agents/{aid}/files/access-urls |
user |
refresh inbound media URLs (signed) |
GET |
/agents/{aid}/files/{path} |
owner |
read an inbound file |
Updates, ollama, i18n, plugins, slash, preferences
| Method |
Path |
Auth |
Notes |
GET/POST |
/update/status, /check, /upgrade, /progress, /restart |
admin |
in-place server update flow |
GET/POST/DELETE |
/ollama/... |
user |
Ollama model discovery + downloads |
GET |
/i18n/tools |
user |
server-owned tool display names (locale-aware) |
GET |
/i18n/locales |
public |
available locales + fallback chain |
GET |
/i18n/locales/{locale}/{namespace} |
public |
one namespace bundle (errors, tools, channel, slash) |
GET/POST |
/preferences |
user |
UI preferences (per-user key/value) |
GET |
/slash/commands |
user |
slash command catalog for the composer menu |
GET/POST |
/plugins |
user |
installed plugin list / install flow |
POST |
/plugins/reload |
admin (plugins) |
reload plugins from disk into process |
PATCH |
/plugins/{id} |
admin (plugins) |
enable/disable plugin ({ "enabled": bool }) |
GET |
/plugins/{id}/ui/{path} |
user |
serve prebuilt plugin UI assets (ui/dist/…) |
DELETE |
/plugins/{id} |
admin (plugins) |
uninstall plugin |
Usage & admin
| Method |
Path |
Auth |
Notes |
GET |
/usage/summary |
user |
token usage summary for the current user |
GET |
/admin/usage |
admin |
global token usage summary |
GET |
/admin/overview |
admin |
{user_count, agent_count, ...} |
GET |
/admin/audit-log |
admin |
recent audit rows |
GET |
/admin/metrics |
admin |
{messages_total, stream_errors_total, cron_runs_total, cron_errors_total, agent_active, ...} |
Envs
| Method |
Path |
Auth |
Notes |
GET/PUT |
/agents/{aid}/envs |
owner |
env-var preset for an agent's tool calls |
GET/POST/DELETE |
/envs/presets |
user |
reusable presets |
Error envelope
Every non-2xx JSON response uses:
code matches the ErrorCode enum in octop.infra.errors; the
server-localized message is rendered by OctopError.to_envelope
with the locale from Accept-Language (falling back to en). The
dashboard mirrors every code under apiErrors.* in
dashboard/src/locales/{en,zh}.json.
| Code |
HTTP |
Meaning |
AUTH_FAILED |
401 |
Bad credentials |
TOKEN_EXPIRED |
401 |
JWT past its TTL |
LOGIN_LOCKED |
423 |
Too many failed attempts — wait login_lockout_seconds or call /users/{id}/unlock-login |
SETUP_REQUIRED |
409 |
Initial admin not yet created (or wizard not finished) |
FORBIDDEN |
403 |
Authenticated but not allowed |
NOT_FOUND |
404 |
No such row / route |
USER_DISABLED |
403 |
Account flag flipped off |
USERNAME_TAKEN |
409 |
Conflict on users.username |
AGENT_NOT_FOUND |
404 |
Agent row missing or owned by another user |
AGENT_FAILED |
500 |
Runtime errored during a call |
AGENT_BUSY |
409 |
Operation refused while another is in flight |
PROVIDER_NAME_TAKEN |
409 |
Conflict on providers.name |
PROVIDER_NOT_VISIBLE |
400 |
Agent config references a provider the user can't see |
PROVIDER_REFERENCED |
409 |
Delete blocked because agents still reference the row |
PROVIDER_TEST_FAILED |
400 |
/providers/{id}/test failed |
CHANNEL_KIND_UNSUPPORTED |
400 |
kind not in registered builders |
CHANNEL_INVALID_CREDENTIALS |
400 |
Channel config rejected by the platform |
CHANNEL_PROBE_INCOMPLETE |
400 |
Probe couldn't reach the platform |
CRON_TRIGGER_INVALID |
400 |
Trigger string did not parse |
CRON_PROMPT_INVALID |
400 |
Empty or too-long prompt |
SLASH_UNKNOWN |
400 |
/<cmd> is not a registered handler |
SLASH_BAD_ARGS |
400 |
Slash handler rejected its arguments |
ATTACHMENT_UNSUPPORTED_TYPE |
400 |
Chat / inbound attachment rejected (media type not allowed) |
ATTACHMENT_TOO_LARGE |
413 |
Chat / inbound attachment exceeds the configured size limit (max_mb in details) |
WORKSPACE_PATH_INVALID |
400 |
Path outside the agent's workspace |
STORAGE_BACKEND_UNREACHABLE |
502 |
Remote backend connect / list failed |
CONNECTOR_OAUTH_FAILED |
400 |
OAuth flow could not complete |
INTERNAL_ERROR |
500 |
Unhandled exception (logged with traceback) |