Skip to content

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.

Terminal window
ocx gui

This 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:

Terminal window
ocx start
bun run dev:gui

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:

  1. run codex login if Codex is not signed in, or open Codex Auth to add/select a pool account;
  2. resync the model catalog from Maintenance; and
  3. 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.

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.

AreaWhat it does
Dashboard summaryMulti-agent mode, online state, version, uptime, provider count, 30-day token total, active providers, and available native/routed models.
Sub-agent delegationChoose 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.
SidecarsChoose the web-search model and effort plus the vision-description model. Changes apply on the next request.
MaintenanceResync 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 safetyShow whether injected Codex routing survives a restart, with separate service and launcher-shim health plus exact repair commands.
Windows trayInstall 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 autostartAllow an already-installed Codex launcher shim to run ocx ensure. This toggle does not install a shim or background service.
ProvidersAdd, 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 providerSearch registry-backed presets for account login, API-key services, local servers, or a custom endpoint.
Codex AuthAdd 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.
SubagentsFeature up to five bare native or namespaced routed models in the spawn_agent override list.
ModelsToggle 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.
LogsAuto-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 / DebugInspect token-usage coverage and trends, or enable opt-in provider transport and usage-extraction diagnostics.
StorageRead-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 connectionManually open another OpenCodex by IPv4, IPv6, or hostname and its active port. The destination dashboard performs ADMIN authentication.
ExportExport dashboard datasets as supported formats/archives. Password-protected 7z is unavailable until a protected password transport exists.
StopGracefully stop the proxy and installed background service, restore native Codex, and exit (POST /api/stop).
Quick restoreTwo 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.
TabsBrowser-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.

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.

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:

  1. Restore. POST /api/host/quick-restore rewrites 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.
  2. Stop. Only then does the dashboard issue the ordinary POST /api/stop, and afterwards it polls /healthz to 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:

RestoreProxyWhat you see
SucceededStoppedOne success notice naming both.
SucceededStill runningA 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.
FailedUntouchedAn error notice with the exact failure. The proxy is deliberately left running so you can retry, or run ocx restore.
No answerUnknownAn 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.

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.

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 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:

BasisWhat it isWhere it comes from
Direct API keyMoney 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 equivalentWhat 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.

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.

BandWhen it appliesEffect on the published standard rate
Fast tierThe 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 contextThe 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_missing instead 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_unmatched rather 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.

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.

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.

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.

The GUI is a thin client over the proxy’s JSON management API. Useful endpoints include:

EndpointPurpose
GET / PUT /api/settingsRead settings or toggle Codex autostart.
GET /api/startup-healthRead secret-free routing, service, shim, and restart-safety diagnostics.
POST /api/startup-actionInstall the background service or Codex launcher shim through fixed, allowlisted actions.
GET / POST /api/windows-trayRead or change the Windows tray installation and visible-process state. POST accepts install, start, stop, or uninstall.
POST /api/syncRebuild the shared model catalog and stale the Codex model cache.
GET /api/update/check · POST /api/update/run · GET /api/update/statusCheck, 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-settingsRead or set search/vision sidecar model settings.
GET / PUT /api/injection-modelRead or set the shared sub-agent model/effort selection and the independent guidance/native-default switches.
GET / PUT /api/v2Read 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-modelsList native/routed model rows and update the shared disabled-model set.
GET /api/selected-models · PUT /api/model-visibilityRead provider allowlists and atomically change the final visibility of one model or provider group.
GET /api/key-providers · GET /api/oauth/providersRead the API-key and OAuth provider catalogs.
POST /api/oauth/login · GET /api/oauth/statusStart a provider OAuth flow and poll for completion.
GET /api/codex-auth/accounts?refresh=1List 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/failoverSelect the account for the next request and configure pool routing.
POST /api/codex-auth/login · GET /api/codex-auth/login-statusAdd a pool account through browser login.
GET /api/logs?tail=50&provider=...&status=5xxRead recent request metadata with optional tail, provider, and exact/class status filters.
GET / PUT /api/subagent-modelsRead 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/hostRead 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/restoreDownload the full state bundle (plaintext secrets), list account-change snapshots, and restore one.
GET / POST /api/host/quick-restoreRead 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/stopStop the proxy/service, restore native Codex, and exit.