On this page
- Health
- Discovery
- Chat
- LLM Providers
- Server Admin
- Sessions
- Telemetry
- Agents
- Persona
- Channels
- Audit
- Evals
POST /api/v1/eval/task-setsGET /api/v1/eval/task-setsGET /api/v1/eval/task-sets/{name}PATCH /api/v1/eval/task-sets/{name}DELETE /api/v1/eval/task-sets/{name}POST /api/v1/eval/task-sets/{name}/tasksPATCH/DELETE /api/v1/eval/task-sets/{name}/tasks/{id}GET /api/v1/eval/task-sets/{name}/exportPOST /api/v1/eval/task-sets/{name}/importPOST /api/v1/eval/runsGET /api/v1/eval/runsGET /api/v1/eval/runs/{id}POST /api/v1/eval/runs/{id}/stopGET /api/v1/eval/runs/{id}/summaryGET /api/v1/eval/runs/{id}/samples
- Safety
- Skills
- Schedules
- Costs
- Approvals
- Auto-Approve Rules
- Setup
- API Keys
- Authentication
- Auth Admin
- KV Store
- Auth Endpoints
- Metrics
- Tools & Plugins
GET /api/v1/toolsGET /api/v1/tools/{name}POST /api/v1/toolsPUT /api/v1/tools/{name}DELETE /api/v1/tools/{name}GET /api/v1/tools/{name}/healthGET /api/v1/tools/{name}/defsPOST /api/v1/tools/{name}/enablePOST /api/v1/tools/{name}/disablePUT /api/v1/tools/{name}/disabled-toolsPOST /api/v1/tools/{name}/restartGET /api/v1/pluginsGET /api/v1/plugins/{name}POST /api/v1/pluginsDELETE /api/v1/plugins/{name}
- MCP OAuth
- Browser
- Rate limiting
REST API Reference
The REST API is enabled with [api] enabled = true in your config. All authenticated endpoints require a Authorization: Bearer dk_... header.
Health
GET /api/v1/health
No authentication required. Returns 200 OK when the server is running.
Discovery
GET /llms.txt
No authentication required. Returns a plain-text summary of this Denkeeper instance intended for LLM clients — base URL, authentication notes, key endpoints, and a list of configured agents with their descriptions. Useful for programmatic discovery when connecting an AI assistant to a Denkeeper instance.
GET /api/v1/openapi.json
No authentication required. Returns the generated OpenAPI 2.0 specification for this instance. This spec is the canonical machine-readable reference — it is generated from the handler annotations and CI-gated against drift, so it is authoritative where this page and the spec disagree.
Chat
POST /api/v1/chat
Scope: chat
Send a message to an agent and receive a response.
Request body:
{
"agent": "default",
"session_id": "optional-session-id",
"message": "Hello!",
"user_id": "api-user",
"user_name": "API User"
}
session_idis auto-generated if omitted. Pass the same value in subsequent requests to continue the conversation.agentdefaults to"default"if omitted.
Response (JSON):
{
"session_id": "abc123",
"response": "Hello! How can I help you?"
}
Response (SSE): Set Accept: text/event-stream for streaming:
data: {"type":"content","text":"Hello! "}
data: {"type":"content","text":"How can I help you?"}
data: {"type":"done","session_id":"abc123"}
SSE event types: content, thinking, tool_start, tool_end, tool_approval, usage, done, error.
GET /api/v1/ws
Scope: chat
Upgrades to a bidirectional WebSocket connection. Authentication is via ?token= query parameter (API key auth) or session cookie. The WebSocket carries the same event types as SSE, plus supports sending chat requests and approval responses as JSON frames.
The web dashboard connects via WebSocket by default and falls back to SSE after 3 failed reconnect attempts. Configure with api.websocket_enabled, api.websocket_max_connections, and api.websocket_replay_buffer_ttl in your config.
GET /api/v1/models
Scope: agents:read
List available LLM models from all configured providers.
GET /api/v1/models/details
Scope: agents:read
Get detailed model information including pricing data.
LLM Providers
GET /api/v1/llm/providers
Scope: admin
List all LLM providers with their current configuration (API keys are redacted).
POST /api/v1/llm/providers
Scope: admin
Create a named provider instance.
Request body:
{
"name": "lmstudio",
"type": "openai",
"base_url": "http://localhost:1234/v1",
"api_key": "lm-studio"
}
DELETE /api/v1/llm/providers/{name}
Scope: admin
Delete a provider instance. Rejected if any agent references it, or if it is the global default_provider.
PATCH /api/v1/llm/providers/{name}
Scope: admin
Update a provider’s configuration (API key, base URL, etc.). Changes take effect immediately and are persisted to config.
Request body:
{
"api_key": "sk-...",
"base_url": "https://api.openai.com/v1"
}
PATCH /api/v1/llm/config
Scope: admin
Update global LLM configuration (default provider, default model).
Request body:
{
"default_provider": "anthropic",
"default_model": "claude-sonnet-4-5"
}
Server Admin
GET /api/v1/server/config
Scope: admin
Server configuration including version, build info, CORS origins, and WebSocket settings.
PATCH /api/v1/server/config
Scope: admin
Update server config (CORS origins, WebSocket settings).
POST /api/v1/server/reload
Scope: admin
Reload the server configuration from disk without restarting.
POST /api/v1/server/restart
Scope: admin
Restart the server process.
Sessions
GET /api/v1/sessions
Scope: sessions:read
List all conversations.
GET /api/v1/sessions/{id}/messages
Scope: sessions:read
Get all messages for a session.
GET /api/v1/sessions/{id}/stats
Scope: sessions:read
Session telemetry summary (model, provider, cost, token breakdown per message).
GET /api/v1/sessions/{id}/tool-calls
Scope: sessions:read
Tool call records for a session (name, server, duration, success/error, round).
GET /api/v1/sessions/{id}/skills
Scope: sessions:read
Skill usage records for a session.
DELETE /api/v1/sessions/{id}
Scope: sessions:write
Delete a conversation and all its messages. Returns 204 No Content. Idempotent.
POST /api/v1/sessions/{id}/clear
Scope: sessions:write
Delete the session’s messages and telemetry, but keep the conversation row — session identity survives, so the same ID continues to work. Accepts an optional ?agent= hint. This is what /clear does in Telegram.
POST /api/v1/sessions/{id}/compact
Scope: sessions:write
Replace the session’s history with a single [Session compacted] summary message. Accepts an optional ?agent= hint.
Response:
{ "summary": "..." }
POST /api/v1/sessions/{id}/stop
Scope: chat
Cancel the in-flight request for a session, if any.
Telemetry
GET /api/v1/telemetry/summary
Scope: costs:read
Aggregate telemetry summary. Accepts ?since= and ?until= query parameters for date filtering.
Agents
GET /api/v1/agents
Scope: admin
List all agents with metadata.
GET /api/v1/agents/{name}
Scope: admin
Get agent details including persona directory, loaded persona sections, and MCP tool names.
POST /api/v1/agents
Scope: admin
Create an agent. Creates the persona directory and persists an [[agents]] block to the TOML config.
Request body:
{
"name": "research",
"llm_provider": "anthropic",
"llm_model": "claude-sonnet-4-5",
"session_tier": "supervised",
"description": "Research assistant",
"create_supervisor": {
"name": "research-supervisor",
"llm_model": "claude-haiku-4-5",
"timeout": "30s",
"context_messages": 5
}
}
create_supervisor is optional and only valid when session_tier is "supervised". When present, the companion supervisor agent is created atomically with the agent it reviews.
PATCH /api/v1/agents/{name}
Scope: agents:write
Update an agent’s configuration. Mutable fields: name (rename), session_tier, llm_provider, llm_model, description, browser_url_allowlist, fallbacks, cost_limit_soft, cost_limit_hard, supervisor, supervisor_timeout, supervisor_context_messages.
DELETE /api/v1/agents/{name}
Scope: admin
Delete an agent and remove its [[agents]] block from the config. Rejected if the agent is referenced by a channel or schedule, is the last remaining agent, or is another agent’s supervisor.
Persona files on disk are not deleted.
Persona
GET /api/v1/agents/{name}/persona/{section}
Scope: agents:read
Read one persona section. {section} is soul, user, or memory — corresponding to SOUL.md, USER.md, and MEMORY.md.
PUT /api/v1/agents/{name}/persona/{section}
Scope: agents:write
Replace a persona section’s contents.
Channels
Named routing endpoints. See the
config reference for the [[channels]] schema.
GET /api/v1/channels
Scope: channels:read
List channels with their agent, adapter bindings, implicit flag, and currently-active adapter keys.
GET /api/v1/channels/{name}
Scope: channels:read
Get one channel. The detail response adds conversation_id.
POST /api/v1/channels
Scope: channels:write
Create a channel.
PATCH /api/v1/channels/{name}
Scope: channels:write
Update a channel.
DELETE /api/v1/channels/{name}
Scope: channels:write
Delete a channel.
POST /api/v1/channels/{name}/activate
Scope: channels:write
Make this channel the active one for a given adapter key — the API equivalent of /session <name>.
Request body:
{ "adapter_key": "telegram:12345" }
DELETE /api/v1/channels/{name}/activate
Scope: channels:write
Clear the active override for an adapter key. Returns 409 Conflict if that key is not currently active on this channel.
Audit
GET /api/v1/audit
Scope: audit:read
List audit events. Filters: ?category=, ?agent=, ?status=, ?source=, ?search=, ?since=, ?until=, ?limit=, ?offset=.
?exclude_source=eval,dryrun omits preview turns. Dry-run and eval events carry the ordinary llm and tool_call categories, so source is the only axis that separates them from live traffic — which is why the exclusion applies to the statistics endpoint too.
GET /api/v1/audit/stats
Scope: audit:read
Aggregate audit statistics. Accepts ?since= and the same ?exclude_source= filter as the list endpoint.
Evals
An eval run compares two or more config variants of one agent over a saved set of test cases and reports an objective scorecard. Samples execute on the agent’s live engine under the same execution policy dry runs use: reads run for real, writes are suppressed, and nothing is persisted to conversations, telemetry, or memory. Runs spend real tokens, bounded by a per-run cost cap and by [eval] max_concurrent.
POST /api/v1/eval/task-sets
Scope: eval:write
Create a named, empty test set. 409 Conflict if the name is taken.
GET /api/v1/eval/task-sets
Scope: eval:read
List test sets with their task counts.
GET /api/v1/eval/task-sets/{name}
Scope: eval:read
One test set with its tasks, in creation order.
PATCH /api/v1/eval/task-sets/{name}
Scope: eval:write
Rename a set or change its description.
DELETE /api/v1/eval/task-sets/{name}
Scope: eval:write
Delete a set and its tasks. Returns 409 Conflict while any run references it — a run’s samples are only interpretable against the tasks that produced them.
POST /api/v1/eval/task-sets/{name}/tasks
Scope: eval:write
Add a test case. This is what the Chat UI’s “Save as test case” calls.
Request body:
{
"prompt": "what's on my plate today",
"category": "chat",
"pinned_history": [{ "role": "user", "content": "earlier turn" }],
"notes": "should list the standup before anything else"
}
category is one of chat, skill_command, scheduled, tool_heavy (default chat). pinned_history is captured now and replayed verbatim at run time rather than re-read from the source conversation, which drifts. notes are judge context, never parsed as assertions.
PATCH / DELETE /api/v1/eval/task-sets/{name}/tasks/{id}
Scope: eval:write
Edit or remove one test case.
GET /api/v1/eval/task-sets/{name}/export
Scope: eval:read
JSONL, one task per line, for hand-editing or git-versioning a curated set.
POST /api/v1/eval/task-sets/{name}/import
Scope: eval:write
Append JSONL tasks. All-or-none: every line is validated first, so a typo halfway down leaves the set untouched and the 400 names the offending line.
POST /api/v1/eval/runs
Scope: eval:write
Create and start a run.
Request body:
{
"task_set": "regression",
"base_agent": "pamela",
"variants": [
{ "name": "incumbent" },
{ "name": "candidate", "llm_model": "moonshotai/kimi-k3" }
],
"k": 3,
"cost_cap": 2.0
}
At least two variants are required. An empty variant runs the agent’s live config; by convention the incumbent is listed first, and per-task deltas are measured against it. k and cost_cap default to [eval] default_k and max_cost_per_run. An unregistered llm_provider is rejected here rather than failing every sample later.
GET /api/v1/eval/runs
Scope: eval:read
List runs newest first. Filters: ?task_set=, ?status=.
GET /api/v1/eval/runs/{id}
Scope: eval:read
Status and progress: samples done out of expected, spend against the cap, and a rough ETA. This is the authoritative view; the eval_progress WebSocket frame is a droppable convenience on top of it.
POST /api/v1/eval/runs/{id}/stop
Scope: eval:write
Cancel an active run. In-flight calls die on the context, queued samples never start, and the run finishes stopped with the samples it already produced. 409 Conflict if the run is already terminal. The panic switch stops every active run the same way; resume does not revive them.
GET /api/v1/eval/runs/{id}/summary
Scope: eval:read
The objective scorecard. Rates are tool-call level with cached and suppressed calls excluded, because nothing executed in either case. A run below [eval] completeness_floor still reports its numbers but is flagged inconclusive.
GET /api/v1/eval/runs/{id}/samples
Scope: eval:read
Per-sample transcripts, including the full tool trace with arguments and results.
Safety
POST /api/v1/panic
Scope: admin
Emergency stop: cancels all in-flight requests and pauses the scheduler.
POST /api/v1/resume
Scope: admin
Clear the panic state and resume the scheduler.
GET /api/v1/panic
Scope: admin
Response:
{ "panicked": true, "panic_time": "2026-08-11T09:15:00Z" }
Panic state is transient — it is cleared by a restart.
Skills
GET /api/v1/skills
Scope: skills:read
List all skills across all agents.
GET /api/v1/skills/{agent}
Scope: skills:read
List skills for a specific agent.
GET /api/v1/skills/{agent}/{name}
Scope: skills:read
Get full skill details including body content.
POST /api/v1/skills/{agent}
Scope: skills:write
Create a new skill. The skill file is written to the agent’s skills directory and registered in memory.
Request body:
{
"name": "daily-report",
"description": "Generate daily summary",
"version": "1.0.0",
"triggers": ["command:report"],
"body": "# Daily Report\nGenerate a summary of today's events."
}
PUT /api/v1/skills/{agent}/{name}
Scope: skills:write
Update an existing skill. Fields are merged with existing values — only provided fields are changed.
Request body:
{
"description": "Updated description",
"version": "2.0.0",
"body": "# Updated content"
}
POST /api/v1/skills/{agent}/{name}/dry-run
Scope: skills:write
Preview what a skill would do without persisting anything. The turn stores no messages, telemetry, or memory; only idempotent tools actually execute, and every other tool call returns a suppressed marker instead of running.
It sits behind the write scope despite persisting nothing, because it executes read tools and spends real tokens.
Request body:
{
"message": "log $12 for coffee",
"mode": "command",
"model": "claude-haiku-4-5",
"as_of": "2026-08-11T08:00:00+10:00",
"args": "--verbose"
}
All fields are optional. mode is schedule, command, or message; omitted, it is inferred — an explicit message implies message semantics, otherwise a command: trigger or a [[schedules]] entry naming the skill decides, defaulting to message. The modes are not cosmetic: only schedule injects the skill body directly, while command and message leave normal trigger matching to run, so a command preview genuinely exercises the trigger.
as_of pins the ## Current Date in the system prompt. model runs the preview against a different model without touching the agent’s live configuration.
Response: a transcript containing prompt, response, rounds, model, requested_model, mode, scheduled_by, tokens_total, cost_usd, suppressed_count, and a tool_calls array in which each entry carries suppressed alongside its outcome.
DELETE /api/v1/skills/{agent}/{name}
Scope: skills:write
Delete a skill. Removes it from memory and deletes the skill file. Returns 204 No Content.
Schedules
GET /api/v1/schedules
Scope: schedules:read
List all schedules with next/last run times.
POST /api/v1/schedules
Scope: schedules:write
Create a new schedule. The schedule is registered in the scheduler and persisted to TOML config.
Request body:
{
"name": "morning-report",
"schedule": "@daily",
"channel": "telegram:123456",
"skill": "daily-report",
"session_mode": "isolated",
"session_tier": "autonomous",
"agent": "default",
"tags": ["reporting"],
"enabled": true
}
schedule: cron expression (0 8 * * 1-5), named (@daily,@hourly), or interval (@every 5m).channel: formatadapter:externalID(e.g.telegram:123456).session_mode:isolated(default) orshared.enabled: defaults totrueif omitted.
PATCH /api/v1/schedules/{name}
Scope: schedules:write
Partially update a schedule. Only provided fields are changed. The schedule is unregistered and re-registered with the new configuration.
POST /api/v1/schedules/{name}/dry-run
Scope: schedules:write
Preview what a schedule would do when it fires, with the same no-persistence semantics as the skill dry-run above. Accepts the optional model and as_of fields.
The preview message is built by the same code the live scheduler uses, so the header, skill, cron expression, and tier match a real firing rather than being reconstructed.
DELETE /api/v1/schedules/{name}
Scope: schedules:write
Delete a schedule. Unregisters it from the scheduler and removes it from the TOML config. Returns 204 No Content.
Costs
GET /api/v1/costs
Scope: costs:read
Get cost summary.
Approvals
GET /api/v1/approvals
Scope: approvals:read
List all approval requests.
GET /api/v1/approvals/{id}
Scope: approvals:read
Get a single approval request.
POST /api/v1/approvals/{id}/approve
Scope: approvals:write
Approve a pending request. Add ?auto_approve=session or ?auto_approve=permanent to simultaneously create an auto-approve rule for future tool calls of the same type.
POST /api/v1/approvals/{id}/deny
Scope: approvals:write
Deny a pending request.
Auto-Approve Rules
GET /api/v1/auto-approve
Scope: approvals:read
List all auto-approve rules. Filter by agent with ?agent=name. The list includes TOML-declared config-scoped rules (auto_approve_tools on [[agents]]); these are read-only and returned with an empty id.
POST /api/v1/auto-approve
Scope: approvals:write
Create an auto-approve rule.
Request body:
{
"agent": "default",
"tool_name": "web_search",
"scope": "permanent"
}
scope:"session"(in-memory, cleared on restart) or"permanent"(persisted in SQLite)."config"is rejected with400— config-scoped rules can only be declared in TOML.
DELETE /api/v1/auto-approve/{id}
Scope: approvals:write
Delete an auto-approve rule. Returns 204 No Content.
Setup
GET /api/v1/setup
No authentication required. Returns the first-run setup status.
POST /api/v1/setup
No authentication required. Initialize the first-run configuration.
POST /api/v1/setup/account
No authentication required, but gated by the one-time setup PIN written to the server logs on first run. Creates the admin account.
Request body:
{ "pin": "482937", "password": "..." }
The PIN is single-use and is never exposed through any API endpoint — reading it requires access to the logs, which is what stops someone who can merely reach the port from claiming the instance.
API Keys
POST /api/v1/keys
Scope: admin
Create a new API key. The plaintext key is returned once in the response.
GET /api/v1/keys
Scope: admin
List all API keys (secrets are never returned).
DELETE /api/v1/keys/{id}
Scope: admin
Revoke an API key by ID.
DELETE /api/v1/keys/{id}/permanent
Scope: admin
Permanently delete a revoked API key.
POST /api/v1/keys/{id}/rotate
Scope: admin
Rotate an API key. Returns the new plaintext key once.
Authentication
All API endpoints (except health, setup, auth, and metrics) require authentication. Two mechanisms are supported:
- Bearer token —
Authorization: Bearer dk_...header. API keys are scoped; a key with onlychatscope cannot access/api/v1/approvals. - Session cookie — set by the password or OIDC login flow. Used by the web dashboard.
curl -H "Authorization: Bearer dk_yourkey" https://localhost:8080/api/v1/approvals
Auth Admin
These endpoints require admin scope.
GET /api/v1/auth/status
Returns auth configuration summary (password enabled, OIDC enabled, session settings, preferred login method).
GET /api/v1/auth/sessions
List all active sessions.
DELETE /api/v1/auth/sessions/{id}
Revoke a session.
DELETE /api/v1/auth/sessions
Revoke every active session at once, forcing all dashboard users to log in again.
POST /api/v1/auth/password
Change the server password. Verifies the current password before re-hashing.
{ "current_password": "old", "new_password": "new" }
GET /api/v1/auth/oidc/test
Test OIDC provider reachability (fresh discovery, 10 s timeout).
POST /api/v1/auth/preferences
Set preferred login method (auto, password, or apikey).
{ "preferred_method": "password" }
GET /api/v1/onboarding
Checklist of 5 setup milestones. show_onboarding is false when all milestones are complete or the card has been dismissed.
POST /api/v1/onboarding/dismiss
Persist onboarding_dismissed = true to the TOML config and hide the onboarding card.
POST /api/v1/onboarding/wizard-complete
Persist wizard_completed = true to the TOML config, marking the guided setup wizard as finished.
KV Store
GET /api/v1/kv/{agent}
Scope: kv:read
List KV keys for an agent. Accepts optional ?prefix= query parameter.
GET /api/v1/kv/{agent}/{key}
Scope: kv:read
Get a value by key. Returns 404 if not found.
PUT /api/v1/kv/{agent}/{key}
Scope: kv:write
Set a value. Body: {"value": "...", "ttl": "5m"} (ttl is optional; omit for no expiry).
DELETE /api/v1/kv/{agent}/{key}
Scope: kv:write
Delete a key.
Auth Endpoints
These endpoints do not require authentication.
GET /auth/config
Returns the server’s authentication configuration.
{
"password_enabled": true,
"oidc_enabled": false
}
POST /auth/login
Password login. Sets a session cookie on success.
Request body:
{
"password": "your-password"
}
Response:
{
"authenticated": true,
"email": "admin"
}
Rate limited: 5 attempts per 15 minutes per IP. Returns 429 Too Many Requests when exceeded.
POST /auth/logout
Clears the session cookie.
{
"ok": true
}
GET /auth/session
Check the current session status.
{
"authenticated": true,
"email": "user@example.com"
}
Returns {"authenticated": false} when no valid session exists.
GET /auth/oidc/login
Redirects to the OIDC provider’s authorization endpoint. Only available when [api.auth.oidc] enabled = true.
GET /auth/callback
OIDC callback. Exchanges the authorization code, verifies the ID token (including nonce), creates a session cookie, and redirects to /#/overview.
Metrics
GET /metrics
Prometheus metrics endpoint. No authentication required. Only available when [otel] enabled = true.
Tools & Plugins
GET /api/v1/tools
Scope: tools:read
List all configured MCP tool servers.
GET /api/v1/tools/{name}
Scope: tools:read
Get details for a specific tool server.
POST /api/v1/tools
Scope: tools:write
Add a new MCP tool server. The tool is started immediately and its configuration is persisted to TOML.
Request body:
{
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
}
PUT /api/v1/tools/{name}
Scope: tools:write
Edit a tool server’s configuration. The server is restarted with the new settings and the configuration is persisted to TOML.
DELETE /api/v1/tools/{name}
Scope: tools:write
Remove a tool server. The process is stopped and the configuration is removed from TOML.
GET /api/v1/tools/{name}/health
Scope: tools:read
Get health status for a specific tool server. Returns connected, error, or disabled status with restart count, last error, and uptime.
GET /api/v1/tools/{name}/defs
Scope: tools:read
List the tool definitions this server advertises.
POST /api/v1/tools/{name}/enable
Scope: tools:write
Enable a tool server, starting its MCP process. Persisted to the TOML config.
POST /api/v1/tools/{name}/disable
Scope: tools:write
Disable a tool server, stopping its process without removing its configuration. Persisted to the TOML config.
PUT /api/v1/tools/{name}/disabled-tools
Scope: tools:write
Set which individual tools from this server are hidden from agents, leaving the server itself running.
POST /api/v1/tools/{name}/restart
Scope: tools:write
Manually restart a tool server.
GET /api/v1/plugins
Scope: tools:read
List all configured plugins.
GET /api/v1/plugins/{name}
Scope: tools:read
Get details for a specific plugin.
POST /api/v1/plugins
Scope: tools:write
Add a new plugin (subprocess or Docker).
DELETE /api/v1/plugins/{name}
Scope: tools:write
Remove a plugin.
Every /api/v1/tools/{name} endpoint returns 404 when the named tool is not registered. Other failures keep their own codes: 400 for malformed or rejected input, 500 for a failed restart or removal, 503 when the lifecycle manager is not wired.
MCP OAuth
For remote SSE tool servers configured with auth = "oauth".
GET /api/v1/tools/{name}/oauth
Scope: tools:read
Get the OAuth token status for a tool.
POST /api/v1/tools/{name}/oauth/connect
Scope: tools:write
Begin the authorization code flow with PKCE. Returns the URL to send the operator to.
DELETE /api/v1/tools/{name}/oauth/token
Scope: tools:write
Revoke and delete the stored token for a tool.
GET /api/v1/tools/oauth/pending
Scope: tools:read
List authorizations awaiting completion.
GET /api/v1/tools/oauth/callback
No authentication — this is the browser redirect target for the provider. Set api.external_url so the callback URL is constructed correctly behind a reverse proxy.
Browser
Available when [browser] enabled = true. These endpoints return 503 when browser automation is not configured.
GET /api/v1/browser/config
Scope: browser:read
Get the effective browser configuration.
GET /api/v1/browser/profiles
Scope: browser:read
List browser profiles.
GET /api/v1/browser/profiles/{name}
Scope: browser:read
Get one browser profile.
DELETE /api/v1/browser/profiles/{name}
Scope: browser:write
Delete a browser profile.
GET /api/v1/browser/sessions
Scope: browser:read
List active browser sessions.
Rate limiting
Per-key rate limiting is configured via api.rate_limit (requests per second). When exceeded, the API returns 429 Too Many Requests.