Open WebUI
BMO can expose an OpenAI-compatible API that lets Open WebUI and other OpenAI-API-compatible clients drive BMO sessions.
Prerequisite: the headless HTTP/SSE hub (bmo serve /
options.http_server) must be running and reachable.
GET /v1/models— Lists the BMO-backed model entries that Open WebUI can show in its model picker.POST /v1/chat/completions— Accepts OpenAI-compatible chat requests and routes them into BMO’s session and agent loop.
Configuration
Section titled “Configuration”[options.openai_compat]enabled = truetool_policy = "auto_approve"Open WebUI uses the same routing, agent selection, and tool policy as the
shared OpenAI-compatible API surface. This page stays focused on the client
boundary: enable options.openai_compat, point Open WebUI at /v1, and use
the shared posture and tracing surfaces below when you need to inspect live
behavior.
If your Open WebUI-backed sessions need MCP-backed capabilities, configure
those servers through the standard [mcp.*] settings and grant them through
the agent policy you want BMO to use. Open WebUI does not require a separate
MCP transport or client-specific tool configuration layer.
Tool policy
Section titled “Tool policy”| Value | Description |
|---|---|
chat_only (default) | No tool_calls in the response; agent uses tools internally but returns plain text |
auto_approve | Tool calls pass through to the client in OpenAI format |
For Open WebUI deployments, use tool_policy = "auto_approve". Open WebUI can
drive BMO’s OpenAI-compatible chat API, but it does not complete BMO’s native
permission challenge flow. With chat_only, prompts that need web research or
other tools can stall while waiting for a permission decision that the client
never sends.
Connecting Open WebUI
Section titled “Connecting Open WebUI”-
Enable the compat API in
bmo.toml -
Choose one of these BMO server setups:
Local-only, unauthenticated BMO (development only)
- Start BMO on loopback:
bmo serve --addr 127.0.0.1:<port> --allow-no-auth - In Open WebUI, set API base URL to
http://127.0.0.1:<port>/v1 - The Open WebUI API key field can be any non-empty string in this mode; it is only satisfying the client UI, not authenticating BMO
Authenticated BMO
- Start BMO with a bearer token:
bmo serve --addr 127.0.0.1:<port> --auth-token <token> - In Open WebUI, set API base URL to
http://127.0.0.1:<port>/v1 - Set the Open WebUI API key to the exact same
<token>so it is sent asAuthorization: Bearer <token>
- Start BMO on loopback:
Open WebUI then shows BMO as a model in the model picker.
Warning: The Open WebUI-side API key field is not, by itself, a BMO security boundary. Keep this integration on loopback by default, or put BMO behind a trusted reverse proxy / access-control layer when exposing it beyond the local machine.
Inspect and operate
Section titled “Inspect and operate”Once BMO is serving, four operator surfaces answer the question “is OpenAI-compat enabled, what’s the resolved policy, and did Open WebUI hit BMO?” without scraping logs by hand.
Use this shortcut when the question starts from the Open WebUI side:
| If you need to know… | Use… |
|---|---|
| Is the feature enabled and how is it configured? | bmo config show-openai-compat or the client-named alias bmo config show-open-webui |
| What is the live feature posture right now? | /openai-compat (alias: /openwebui) or GET /v1/openai-compat/posture |
| Did this request hit BMO and how did it end? | GET /v1/openai-compat/runs, GET /v1/openai-compat/runs/{id}/events, and the tracing recipes below |
| Was this a session-reuse, queued, resumed, or auth-boundary case? | The openai_compat.* tracing recipes in agent tracing |
| Which surfaces are shared with the generic OpenAI-compatible API? | The shared openai_compat posture family; Open WebUI does not have a separate HTTP, agent, or MCP status transport |
HTTP routes
Section titled “HTTP routes”| Route | Purpose |
|---|---|
POST /v1/chat/completions | OpenAI-compatible chat — accepts streaming and non-streaming requests |
GET /v1/models | Advertises the BMO-backed model entries Open WebUI shows in its picker |
GET /v1/openai-compat/posture | Summarizes live feature posture, bounded per-route counts, and top client User-Agents |
GET /v1/openai-compat/runs | Lists recent compat runs from the durable run ledger |
GET /v1/openai-compat/runs/{id}/events | Streams the per-run terminal-arm event timeline |
The two /v1/openai-compat/runs* routes are the inspection counterparts of the
chat surface: every request that reaches BMO records a row in the run ledger
plus a terminal-arm event row, both queryable after the fact.
CLI: bmo config show-openai-compat
Section titled “CLI: bmo config show-openai-compat”bmo config show-openai-compat prints the resolved [options.openai_compat]
configuration after layered config merge — the same shape the running server
sees. Sibling of bmo config show-ripple, show-file-tools, show-loop-detection,
show-length-recovery, and show-spawning.
If you search by consumer name instead of protocol name, use the alias
bmo config show-open-webui; it resolves to the exact same config-only output.
Output covers:
enabledflagtool_policy(chat_onlyorauto_approve)ambiguity_action(ask,research,code)model_override_policy(rejectorallow_ephemeral)- Advertised model IDs (the same set
/v1/modelsreturns) - Bind boundary status — whether
--auth-tokenis set, whether the bind address is loopback, or(check at serve-time)if the runtime flags are not reachable from the config-show context - Operator warning that mirrors this page’s loopback warning when the effective bind appears unsafe
- A pointer to the live posture family (
/openai-compat,get_openai_compat_status, orGET /v1/openai-compat/posture) for route/client aggregates
Run bmo config show-openai-compat --help for the full flag list and example
output.
TUI: /openai-compat slash
Section titled “TUI: /openai-compat slash”From inside the TUI, /openai-compat (aliases: /openwebui) renders a
read-only status report:
- Enabled flag and resolved tool/ambiguity/model-override policies
- Advertised model IDs
- Run ledger size
- Per-route request counts and the top-5 client User-Agents
- Last 5 terminal outcomes from the
openai_compat_run_eventstable — each row shows truncatedrequest_id, elapsed-ms badge, and theopenai_compat.actionoutcome string - Sticky
degraded — persist failureswarning when the run ledger has observed an unrecovered persistence error
Mutation (enabling, changing tool_policy) stays in bmo.toml and is
inspected with the CLI subcommand above; the slash is intentionally
read-only.
Sidebar indicator
Section titled “Sidebar indicator”The TUI sidebar shows a one-line OpenAI compat: status with four documented
states:
| Label | When |
|---|---|
disabled | enabled = false (or no [options.openai_compat] block) |
enabled (chat_only) | Enabled and tool_policy = "chat_only" (the default) |
enabled (auto_approve) | Enabled and tool_policy = "auto_approve" |
degraded — persist failures | Run ledger persistence has failed and not yet recovered (overrides the policy label until persistence succeeds again) |
The label is a pure function of the resolved config plus the run ledger’s sticky degraded flag — same source the slash command renders.
Tracing and bmo logs recipes
Section titled “Tracing and bmo logs recipes”Each compat request emits exactly one openai_compat.fired slog record at
route entry and exactly one terminal openai_compat.action record per
request, sharing a request_id field that also keys the SQLite run ledger.
For copy-paste filter recipes that answer “did Open WebUI hit BMO and what happened?”, including session reuse, queued/resumed, and auth/proxy-boundary questions, see agent tracing — Open WebUI / OpenAI-compat.
openai_compat.fired field set
Section titled “openai_compat.fired field set”| Field | Type | When set |
|---|---|---|
request_id | string | Always — pairs fired with its closing action and keys the run ledger row |
session_id | string | Always (empty when no X-BMO-Session-Id header) |
route | string | One of chat_completions, models_list, runs_list, run_events_list |
tool_policy | string | chat_only or auto_approve |
ambiguity_action | string | ask, research, or code |
model | string | Resolved model ID for the request |
stream | bool | true when the client requested an SSE stream |
is_session_reuse | bool | true when X-BMO-Session-Id was set on the request |
client_ua | string | User-Agent header, truncated at 64 bytes |
openai_compat.action outcome enum
Section titled “openai_compat.action outcome enum”| Outcome | Severity | Description |
|---|---|---|
final_answer | info | Terminal happy path — assistant content delivered to the client |
clarification | info | Ambiguity router returned a clarification body without running the agent |
queued | info | Shared-session-busy — second request received 409 with the queued-error body |
resumed | info | Stream resumed from an existing queued run’s events |
cancelled | info | Client disconnected mid-stream; agent run cancelled and ledger event recorded |
model_override_applied | info | Per-request model override accepted (allow-ephemeral path) |
model_override_rejected | info | Per-request model override rejected; route returned 400 |
auth_failed | info | Auth boundary refused the request — expected operator-visible outcome |
recovery_failed | warn | Post-tool recovery prompt also failed to produce a final answer |
k8s_retry_failed | warn | Kubernetes inspection retry path exhausted without an answer |
persist_failed | warn | Run-ledger or run-events persistence failed; response still served |
error_500 | warn | Generic 500 — list endpoints, malformed input handlers, unrecoverable arms |
model_override_rejected and auth_failed are intentionally Info — they are
expected boundary outcomes operators want to graph, not error noise. Per the
same convention, cancelled is Info because client disconnects are routine.
Warning: The Open WebUI-side API key field is not, by itself, a BMO security boundary. The new operator surfaces above (CLI, slash, sidebar, filter recipes) make the integration easier to inspect, but they do not change the bind boundary. Keep this integration on loopback by default, or put BMO behind a trusted reverse proxy / access-control layer when exposing it beyond the local machine.
Trusted requester identity
Section titled “Trusted requester identity”If you run BMO behind Envoy or another reverse proxy and want BMO to record the
authenticated requester on sessions, permissions, and operation audit records,
set BMO_REQUESTER_TRUSTED_PROXIES to the proxy IPs or CIDRs that are allowed
to forward X-Auth-* identity headers. BMO ignores those headers on direct
client traffic by default.
Use cases
Section titled “Use cases”- Use Open WebUI’s chat interface while BMO handles the coding agent loop in the background
- Route requests through Open WebUI’s access controls before they reach BMO
- Test BMO responses with OpenAI-compatible evaluation tooling
Related
Section titled “Related”- Protocol support - compare MCP, ACP, and A2A at a glance.
- MCP Client - connect external MCP servers to BMO.
- MCP Server - expose BMO tools to other clients.
- Zed - another editor-facing integration path.