Web Dashboard
opencodex ships a local web dashboard (a Vite/React app under gui/) served from the proxy. It is the
shortest path to managing providers, Codex/ChatGPT accounts, catalog models, sidecars, sub-agent
settings, and request traffic.
Opening it
Section titled “Opening it”ocx guiThis opens http://localhost:<port> in your browser, auto-starting the proxy first if needed. In
development you can run the GUI dev server separately against a running proxy:
ocx startbun run dev:guiFirst run: no provider API key
Section titled “First run: no provider API key”A fresh config already contains the built-in openai ChatGPT-forward provider. The dashboard treats
forward, OAuth, local, key-optional, and loopback providers as configured without requiring an
apiKey field; a key-authenticated provider still needs a nonblank direct or pooled key. Arbitrary
custom headers do not count as that credential. Disabled providers remain disabled.
Configured status describes the saved route, not live upstream health. The default route still needs a valid ChatGPT/Codex login. If no native models are available:
- run
codex loginif Codex is not signed in, or open Codex Auth to add/select a pool account; - resync the model catalog from Maintenance; and
- add a provider key only if the selected route is actually a key-authenticated provider.
The dashboard’s no-model guidance therefore points to provider configuration or account sign-in, not to API keys alone.
Remote access and admission keys
Section titled “Remote access and admission keys”The dashboard never asks for an admin token. /api/* management routes are intentionally open, so
loopback and remote pages use the same request path and a proxy restart cannot produce a credential
dialog. Data-plane /v1/* authentication remains separate. If you expose the proxy beyond
loopback, place it behind an external authenticated boundary because management routes include
provider settings, account controls, exports, and logs.
After setup, the app bar keeps a Connect to another OpenCodex action available. It opens a
manual endpoint dialog for an IPv4/IPv6 address or host name, pre-filling the standard 10100
port while allowing a different validated port. Discovery is only a convenience; Wi-Fi isolation,
firewalls, and separate networks can hide a working remote, so manual entry is intentionally
available even when discovery reports nothing. A successful entry opens that remote’s dashboard
in a new window, and reopening the dialog starts with a fresh host and the standard port.
The manual endpoint accepts only host names and IP literals and constructs an http:// dashboard
URL; it does not accept a pasted scheme, path, credentials, or query string. This keeps the
connection action scoped to the remote dashboard rather than turning the address field into an
arbitrary browser navigation control.
[!NOTE] The app is currently a browser-served dashboard. Connecting opens another dashboard window; it does not move the current process, credentials, provider configuration, or local history to the other machine.
On a non-loopback bind, /v1/* clients must send the OpenCodex admission key generated by
ocx host enable --new-key --yes. That key protects this proxy; it is not an OpenAI or other
upstream provider key. Local localhost data-plane traffic does not require it. A response saying
opencodex API key required means the caller omitted this admission key on a non-loopback route,
not that the built-in ChatGPT-forward provider needs an API key.
The mobile remote uses the same open management surface. Enabling remote access from Remote access & backup generates the data-plane key for you rather than asking you to invent one, and Pair a phone shows a QR code carrying a one-time pairing code alongside the address — one code per network address the proxy answers on. The phone spends the code on arrival and keeps a data-plane key of its own, saved in that phone’s browser so it never scans twice. The phone can send requests through the proxy and can reach the same management routes as any other client. See Pairing a phone with a QR code.
What you can do
Section titled “What you can do”| Area | What it does |
|---|---|
| Dashboard summary | Multi-agent mode, online state, version, uptime, provider count, 30-day token total, active providers, and available native/routed models. |
| Sub-agent delegation | Choose a native or routed model and optional reasoning effort shared by OpenCodex delegation guidance and the separate native-default opt-in. This is not a proxy-side per-spawn router; see below. |
| Sidecars | Choose the web-search model and effort plus the vision-description model. Changes apply on the next request. |
| Maintenance | Resync the Codex model catalog, inspect project-local config bypass warnings, check the latest or preview release, and run an update with optional proxy restart. |
| Startup safety | Show whether injected Codex routing survives a restart, with separate service and launcher-shim health plus exact repair commands. |
| Windows tray | Install a per-user login tray for one-click proxy start, stop, restart, dashboard access, and status. The tray is a controller, not a proxy restart service. |
| Codex autostart | Allow an already-installed Codex launcher shim to run ocx ensure. This toggle does not install a shim or background service. |
| Providers | Add, edit, enable/disable, and remove providers; manage OAuth account pools and API-key pools where supported. Provider Settings can disable live model discovery for endpoints with missing, slow, or oversized /models catalogs. For Claude (Anthropic) OAuth pools, each logged-in account shows its own 5-hour and weekly rate-limit bars (usage is per credential); a failed probe keeps the last-known bars and marks them unavailable until the next successful refresh. |
| Add provider | Search registry-backed presets for account login, API-key services, local servers, or a custom endpoint. |
| Codex Auth | Add ChatGPT/Codex pool accounts, select the next-session account, refresh 5h / weekly / 30d quotas, enable or disable quota auto-switch, set its 1–100% threshold, and configure transient-failure failover. |
| Subagents | Feature up to five bare native or namespaced routed models in the spawn_agent override list. |
| Models | Toggle native GPT and routed models, set provider allowlists and context caps, choose v1/base/v2, and configure the v2 thread limit. Configured providers stay visible as zero-model groups when discovery is off or returns no rows. |
| Logs | Auto-refresh recent requests with tokens, requested effort and (when available) effective outbound effort, resolved model, provider, status, request id, duration, and error details. The detail view includes the exact reasoning wire field when the adapter emits one. Filter by opaque conversation/session id (when the client sends one) to total tokens and estimated list-price cost for the currently loaded Logs ring. |
| Usage / Debug | Inspect token-usage coverage and trends, or enable opt-in provider transport and usage-extraction diagnostics. |
| Storage | Read-only CODEX_HOME disk breakdown (sessions, archives, DBs, attachments). Optional archived cleanup: preview the oldest N%, then quarantine to CODEX_HOME/.trash (default) or permanently delete behind an explicit checkbox. Auto-cleanup policy is opt-in and default OFF (storageCleanupPolicy.enabled); configure threshold/target/schedule/mode on the Storage page, or trigger Run now. Quarantined entries can be restored from the Storage page (JSONL + threads). Active sessions stay read-only. Cleanup and restore are refused while Codex holds the newest/active state_*.sqlite locked. |
| Remote connection | Manually open another OpenCodex by IPv4, IPv6, or hostname and its active port. The destination dashboard performs ADMIN authentication. |
| Export | Export dashboard datasets as supported formats/archives. Password-protected 7z is unavailable until a protected password transport exists. |
| Stop | Gracefully stop the proxy and installed background service, restore native Codex, and exit (POST /api/stop). |
| Quick restore | Two app-bar actions that hand Codex or Claude its own configuration back and then stop the proxy. The restore runs first and is not conditional on the stop, so it still completes when a normal stop is stuck. See below. |
| Tabs | Browser-style tabs with pinning, drag reordering, an overflow menu, per-tab appearance, named and collapsible groups with an anchored “Move… into group…” picker, and four searches that find a tab across this strip, inside a group, by group name, or across every window. See Tab Groups & Tab Search. |
Linking to a section
Section titled “Linking to a section”There is a single layout, so there is no layout switch to configure. Dashboard sections are
addressable instead: #dashboard opens Overview, and #dashboard/providers and
#dashboard/models open the other two. Reload, bookmark, and Back all keep the section you were
on. Logs works the same way with #logs and #logs/debug. An older #providers/workspace
bookmark now lands on #providers.
Quick restore
Section titled “Quick restore”OpenCodex works by rewriting the files your agent tools read — $CODEX_HOME/config.toml and the
routed model catalog for Codex, ~/.claude/agents/ocx-*.md and (on macOS) the injected Claude
environment variables and ~/.zshrc hook for Claude. Stop, ocx stop and ocx restore all
undo that on the way out.
Quick restore exists for the case where that is not enough. A normal stop drains in-flight requests
before it touches a file, and it refuses outright when an installed background service belongs to a
different OPENCODEX_HOME. In each of those the stop fails and the rewritten config stays
rewritten, which is the one thing you needed back.
So the app bar carries a Restore Codex and a Restore Claude action that run the two halves as separate steps, in this order:
- Restore.
POST /api/host/quick-restorerewrites only that tool’s own files. It does not drain, does not touch the background service, and does not exit, so nothing in the proxy’s shutdown path can hold it up or refuse it. - Stop. Only then does the dashboard issue the ordinary
POST /api/stop, and afterwards it polls/healthzto confirm the listener really went away rather than assuming a dropped connection meant success.
Because they are separate requests, both outcomes are always known and both are always reported:
| Restore | Proxy | What you see |
|---|---|---|
| Succeeded | Stopped | One success notice naming both. |
| Succeeded | Still running | A success notice for the restore and a persistent error notice saying the proxy did not stop, with the reason. Your config is native again, but starting or syncing OpenCodex re-applies the routing, so run ocx stop from a terminal. |
| Failed | Untouched | An error notice with the exact failure. The proxy is deliberately left running so you can retry, or run ocx restore. |
| No answer | Unknown | An error notice saying so. Nothing is claimed in either direction — check ocx status, then ocx restore. |
Before writing anything, OpenCodex commits your current ~/.opencodex state to the local Version
history — the same append-only snapshot the Restore-a-snapshot flow uses, so the action is on the
record. That snapshot is bounded: if it cannot finish in five seconds it is abandoned and the
notification says it was not recorded, because the restore matters more than the audit line.
Note what that snapshot does and does not cover. It records OpenCodex’s own state
(config.json, codex-accounts.json, auth.json), not the tool config being rewritten. Getting the
proxy routing back is not an undo of this action at all — it is ocx start, or starting OpenCodex
from the dashboard, which re-applies the injection from scratch.
An action is disabled, with the reason on the control itself, when that tool is not configured on
this machine, or when the dashboard could not read the restore status from the proxy. Both routes
are refused when OPENCODEX_DEBUG_SANDBOX is set (they write real files, which the sandbox exists
to prevent) and when the proxy is listening on anything other than 127.0.0.1 — a management
credential presented over the LAN must not become the ability to reconfigure the installed software
on the host.
Below the expanded breakpoint, or while unapplied settings drafts are showing, the two actions collapse into a single app-bar button that opens a panel containing the same two actions, so they never crowd the window controls off the end of the row.
Connect to another OpenCodex
Section titled “Connect to another OpenCodex”Open Connect to another OpenCodex from the navigation rail or mobile menu. Enter an IPv4
address, IPv6 address, or DNS hostname and a port from 1 through 65535. Port 10100 is filled in by
default; replace it with the remote’s identity-verified active listener port from
ocx host status (or ocx status) when that host started on a fallback port.
Connect opens the exact validated HTTP origin at #/dashboard in a new tab. The dialog does not
probe the host, add a token to the URL, or save one. The destination dashboard prompts for that
proxy’s ADMIN token, which is distinct from its data-plane API keys. HTTP is unencrypted, so connect
directly only across a trusted LAN and prefer an SSH tunnel for other networks.
Export archives
Section titled “Export archives”7z export remains available when a safe 7-Zip executable is installed, but password encryption is disabled. 7-Zip accepts passwords only through process arguments, where other local processes may observe them; OpenCodex will not enable encryption until a protected password-input channel exists. Use an unencrypted 7z/ZIP for non-secret datasets, or protect a sensitive export outside OpenCodex with an appropriate secret-safe tool.
Cost figures and what they mean
Section titled “Cost figures and what they mean”Cost values in Logs and Usage are calculated from reported tokens against published price schedules. They are never billing receipts or evidence of an actual charge.
OpenCodex keeps two separate kinds of cost figure and never adds them together, because they mean different things:
| Basis | What it is | Where it comes from |
|---|---|---|
| Direct API key | Money actually owed, at the provider’s published list rate. | Requests sent with your own API key — the OpenAI, Anthropic, DeepSeek and Moonshot API-key providers. |
| API equivalent | What the same traffic would have cost on the API. Nothing is charged. | Requests sent over a subscription or OAuth plan — ChatGPT/Codex and Claude sign-in. |
An API-equivalent figure is always shown with a not billed tag beside it, in the app-bar cost
chip, the Usage tiles, the Logs table and detail panel, and the provider workspace. It exists so a
subscription plan shows the real value of its traffic instead of a misleading $0; the tag exists so
that figure is never mistaken for a bill. Where both kinds of traffic are present, the billable total
is the headline and the equivalent total is listed separately rather than folded into it.
Some traffic has no published price — a provider with no schedule OpenCodex can cite. Those
requests show an em dash and say so. That is deliberately not the same as $0: zero is a claim that
something was free, and an absent price is a claim about nothing at all. They are counted separately
as unpriced requests in the Usage coverage panel.
Price bands: Fast tier and long context
Section titled “Price bands: Fast tier and long context”A model can publish more than one rate for identical tokens. OpenCodex prices each band from its own schedule row, so a total that looks unexpectedly large is traceable to a published rate rather than to an arithmetic slip. Both bands apply to both cost bases above — the API-equivalent comparison reads exactly the same schedules as direct billing, or it would stop being comparable.
| Band | When it applies | Effect on the published standard rate |
|---|---|---|
| Fast tier | The request was sent with OpenAI’s Fast mode (service_tier=priority, also reported as fast). | One uniform factor on every token type: ×2 for gpt-5.6-sol, gpt-5.6-terra and gpt-5.6-luna; ×2.5 for gpt-5.5. |
| Long context | The raw request prompt is more than 272,000 tokens. | ×2 input, ×1.5 output, ×2 cache read and ×2 cache write, for the whole request. |
The long-context threshold is exclusive: a prompt of exactly 272,000 tokens is still priced at the short-context rate, and the band begins at 272,001. The measured quantity is the raw prompt size recorded when the request was sent, not the billable input left after cache tokens are subtracted — a 280,000-token prompt served largely from cache still crosses the boundary.
Where a band applied, the Logs detail panel names it beside Cost basis, with the factor that was applied, so a doubled figure explains itself.
Two cases are deliberately left unpriced rather than estimated:
- No recorded prompt size. The prompt size is what selects between the short and long bands, so a
request that never recorded one cannot be priced at either. This affects rows written before
OpenCodex began persisting that metric; they report
pricing_context_missinginstead of being assumed short, because assuming short would silently halve every long request among them. - A Fast request above the long-context boundary. OpenAI does not serve long context in Fast
mode, so such a request was served as something else — and the recorded tier alone cannot say
which. It reports
pricing_condition_unmatchedrather than guessing between two rates.
A model with no published Fast rate has no Fast row at all, so a Fast request against it is unpriced rather than quietly billed at the standard rate.
Model visibility
Section titled “Model visibility”The Models switches show final Codex visibility: a routed model is on only when its provider allowlist includes it (or no allowlist is set) and it is not disabled. Turning a model on reconciles both filters atomically; All on clears the provider allowlist so newly discovered models are also on.
Delegation picker vs spawn routing
Section titled “Delegation picker vs spawn routing”The Dashboard’s Sub-agent delegation picker stores injectionModel and, optionally,
injectionEffort. OpenCodex multi-agent guidance independently controls the delegation
instructions that use those values. On eligible v2 turns, that guidance tells the parent
agent which exact model and reasoning effort to pass to spawn_agent; clearing the model also clears
the stored effort.
The default-off Use as native Codex subagent defaults switch applies the same selection to Codex’s
native [agents] defaults on the next sync/restart when OpenCodex manages the active Codex routing.
External user-managed provider configs remain untouched. Those defaults affect newly created Codex tasks
and do not themselves cause delegation. Existing user-owned [agents] defaults are preserved rather
than overwritten, so they may continue to override the requested defaults.
The spawn override guarantee applies to the built-in v2 guidance text. A custom
injectionPrompt replaces that text entirely and must include {{model}} and {{effort}}
placeholders (and optionally {{roster}}) or those values will not appear in the injected
guidance.
The picker offers enabled native and routed models plus the global Codex effort ladder. The API validates the selected effort globally; Codex still validates a spawn effort against the target catalog entry.
Codex Auth and account pools
Section titled “Codex Auth and account pools”The Codex Auth page manages the native ChatGPT/Codex route:
- Manually choosing an account changes the next new Codex session; an already-bound thread keeps its current account for that manual switch.
- Thread affinity prevents per-request flapping. With quota auto-switch enabled, a long-running thread is periodically re-evaluated and may rebind after its relevant usage reaches the threshold and a strictly lower-usage eligible account exists.
- New sessions can choose the lowest-usage eligible account. Paid plans score the hottest known 5h, weekly, or 30d window; Go/Free plans use the 30d window only.
- When WHAM supplies
limit_window_seconds, Codex Auth classifies a primary window of at least 28 days as 30d instead of assuming every primary window is weekly. Responses without a duration keep the legacy weekly interpretation. - Refresh quotas re-reads account usage immediately so routing and the account cards use the same values.
- Browser OAuth can add an authenticated account whose relevant WHAM quota is already 100% used. The validation request that would otherwise be sent is skipped for that account, because a spent account cannot answer it and the authenticated usage read already proved the credential. The account remains visible at 100%, but confirmed exhaustion is distinct from unknown quota and keeps the account out of automatic routing until a successful refresh reports recovery. Every other account — including one whose usage probe failed — is still validated, and manual credential import always is.
- Other OAuth provider accounts also remain stored when optional quota telemetry is unavailable; the dashboard reports unavailable usage instead of turning a valid login into an authentication error.
- Pool request logs use opaque labels such as
p3fa91c, never account emails.
How the dashboard talks to the proxy
Section titled “How the dashboard talks to the proxy”The GUI is a thin client over the proxy’s JSON management API. Useful endpoints include:
| Endpoint | Purpose |
|---|---|
GET / PUT /api/settings | Read settings or toggle Codex autostart. |
GET /api/startup-health | Read secret-free routing, service, shim, and restart-safety diagnostics. |
POST /api/startup-action | Install the background service or Codex launcher shim through fixed, allowlisted actions. |
GET / POST /api/windows-tray | Read or change the Windows tray installation and visible-process state. POST accepts install, start, stop, or uninstall. |
POST /api/sync | Rebuild the shared model catalog and stale the Codex model cache. |
GET /api/update/check · POST /api/update/run · GET /api/update/status | Check, run, and monitor self-update jobs. Worker PIDs are persisted so a crashed job recovers automatically; legacy no-PID jobs recover after ten minutes. |
GET / PUT /api/sidecar-settings | Read or set search/vision sidecar model settings. |
GET / PUT /api/injection-model | Read or set the shared sub-agent model/effort selection and the independent guidance/native-default switches. |
GET / PUT /api/v2 | Read or set the surface mode, Codex feature flag, and v2 thread limit. |
GET /api/providers · POST /api/providers · PATCH /api/providers?name=... · DELETE /api/providers?name=... | List, add/replace, enable/disable, or remove providers. |
GET /api/models · PUT /api/disabled-models | List native/routed model rows and update the shared disabled-model set. |
GET /api/selected-models · PUT /api/model-visibility | Read provider allowlists and atomically change the final visibility of one model or provider group. |
GET /api/key-providers · GET /api/oauth/providers | Read the API-key and OAuth provider catalogs. |
POST /api/oauth/login · GET /api/oauth/status | Start a provider OAuth flow and poll for completion. |
GET /api/codex-auth/accounts?refresh=1 | List main and pool accounts, force quota refresh, and report main-account hasCredential / terminal needsReauth state. |
PUT /api/codex-auth/active · PUT /api/codex-auth/auto-switch · PUT /api/codex-auth/failover | Select the account for the next request and configure pool routing. |
POST /api/codex-auth/login · GET /api/codex-auth/login-status | Add a pool account through browser login. |
GET /api/logs?tail=50&provider=...&status=5xx | Read recent request metadata with optional tail, provider, and exact/class status filters. |
GET / PUT /api/subagent-models | Read or set the five featured spawn_agent override models. |
GET / PUT /api/oauth/accounts/pool?provider=... | Read or change one OAuth provider’s experimental account pool. 409 when a non-Anthropic provider is not in the config, because there is nowhere to store the setting. |
GET / PUT /api/host | Read the bind status and LAN URLs, or expose/unexpose the proxy. A minted data-plane key is returned in that one response and never again. |
GET /api/host/export · GET /api/host/history · POST /api/host/restore | Download the full state bundle (plaintext secrets), list account-change snapshots, and restore one. |
GET / POST /api/host/quick-restore | Read per-tool restore readiness (present on this machine, files affected, routing currently injected), or hand one tool ({"tool":"codex"} / {"tool":"claude"}) its native configuration back. The POST rewrites files only — it never drains, stops the service, or exits. Loopback listener required; refused under OPENCODEX_DEBUG_SANDBOX. |
POST /api/stop | Stop the proxy/service, restore native Codex, and exit. |

