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 — or http://127.0.0.1:<management port> when hub
management ingress is enabled — 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:guiSign-in
Section titled “Sign-in”On the default loopback bind (localhost / 127.0.0.1) the dashboard never asks for a token:
the proxy mints short-lived GUI sessions into the served page and renews them silently when
they expire or the proxy restarts. Only a dashboard bound to a non-loopback hostname requires
the admin token (OPENCODEX_ADMIN_AUTH_TOKEN, or the auto-generated
~/.opencodex/admin-api-token file).
When a remote dashboard needs that credential, it presents a standard password form so a browser
password manager can offer to save and autofill it. The dashboard itself still keeps the token only
in memory and does not write it to localStorage or sessionStorage; whether it is saved is entirely
the browser or password manager’s decision.
Finding the admin token
Section titled “Finding the admin token”You only need this on a non-loopback bind. A local dashboard never asks, and if a local one does ask, the token is not the problem — see When a local dashboard cannot start a session below.
The proxy generates the token for you the first time it starts. It is not printed anywhere, by design, so read it from the file:
cat ~/.opencodex/admin-api-tokenIf OPENCODEX_HOME is set, the file lives at $OPENCODEX_HOME/admin-api-token instead. On
Windows that is %USERPROFILE%\.opencodex\admin-api-token. A generated token looks like
ocx_admin_ followed by 43 characters; the proxy refuses a file that does not match that
shape rather than silently regenerating one.
To choose the value yourself, set OPENCODEX_ADMIN_AUTH_TOKEN before starting the proxy. It
takes precedence over the file, and the file is then neither read nor created. Pick something
distinct from your data-plane credential (OPENCODEX_API_AUTH_TOKEN or a configured API key) —
reusing one is rejected.
There is no CLI command that prints the token. ocx doctor deliberately reports whether a
credential is present without ever revealing its value.
When a local dashboard cannot start a session
Section titled “When a local dashboard cannot start a session”A dashboard on localhost mints its own session, so it will not prompt you for a token. If it
reports that it could not start a session, the cause is the address you are using rather than a
missing credential — the proxy did not recognise the request as loopback. Open the dashboard at
the address the proxy prints on startup (usually http://127.0.0.1:<port>), and prefer that exact
host and port over a LAN IP or an alias.
Dashboard layout
Section titled “Dashboard layout”The sidebar has eight rows, in order: Dashboard, Connect, Codex, Providers, Models, Subagents, Usage & Logs, and Remote Link. Connect opens the page headed Connect, with its tab strip ordered Codex, Claude, Claude Desktop, Grok Build, then the remaining integrations. Claude has no sub-tabs; it shows the Claude Code settings directly. Claude Desktop is a separate Connect tab at #claude/desktop. Connect has no section switcher. Subagents opens Subagents and groups Subagents and Advisor (#advisor) with a pill-shaped section switcher. Usage & Logs opens Usage and groups Usage, Logs & Debug, and Storage with a pill-shaped section switcher. The last row, Remote Link, groups Remote Link (#remote) and Remote Workspace (#remote-workspace, when available) with the same switcher. Codex is the renamed Codex Set row; the preserved URLs include #codex-set, #claude, #claude/code, and #claude/desktop; the former #claude/settings bookmark opens Code. The former #claude/account bookmark redirects to #providers?provider=anthropic&tab=accounts. Startup (#startup) stays outside the sidebar.
Responses first-output timing includes streamed function arguments and custom-tool input, as well as text and reasoning. A tool-only turn can therefore have a first-output time even without prose. Empty deltas and tool-start notifications do not start this timer. It measures the proxy’s first observed output, not the start of hidden model reasoning or exact model decoding throughput.
Overview uses matching status cards and full-width settings rows. On wide screens, labels share one column and model/effort controls share another. On narrower screens, controls move below their labels in the same reading order. Long version labels are shortened visually; hover the version badge or the version value to read the full value.
Quota summary bar
Section titled “Quota summary bar”A one-line summary at the top of every page except the Startup page shows each provider’s current
quota usage, for example OpenAI 31% | Claude 54% | xAI 12% | Google 8%. It reads the same provider
quota reports as the Providers workspace (GET /api/provider-quotas, every 60 seconds while the tab
is visible) and never forces an upstream refresh.
- Each chip shows the preferred reported window: weekly first, then monthly, then 5-hour, then a provider-named window or prepaid credits.
- A chip turns amber at 70% used and red at 90% used.
- Hover or keyboard-focus a chip to see every reported window with its reset time and the time the reading was taken. The details stay open as the pointer moves from the chip into the popover, including across the small visual gap. On a touch screen, the first tap shows those details.
- Click a chip (or tap it a second time) to open that provider’s Accounts tab in Providers, where its accounts or API keys are managed. The popover’s Open account management link opens the same tab; keyboard users can Tab from the chip to this link and press Enter. Escape closes the popover and returns focus to the chip when focus was inside the popover.
- The bar always stays on one line. When the chips do not fit, scroll it sideways or use the « and » buttons at either end.
- Providers that report no quota window are left out. The bar is hidden when no provider reports one.
- The right edge shows when the dashboard last read the reports. It turns amber when the latest read failed and the previous reading is still shown.
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. |
| Native reasoning retention | Independently opt into reasoning ciphertext forwarding on native model or account changes. Both default off; account retention is experimental. See the configuration scope. |
| 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. In the desktop shell, its update entry opens the native app update page instead of running the package updater. |
| 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, set the default (enabled providers only), enable/disable, and remove providers; manage OAuth account pools and API-key pools where supported. Removing the current default switches to the first remaining enabled provider when one exists; otherwise deletion is refused and the current default is kept. 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. The Provider Overview shown when no provider is selected carries a Refresh all quotas control that forces one server-side re-read of every configured provider; a provider whose upstream probe fails keeps its last-good row, so the status line reports that the check completed rather than claiming every value is fresh, and each row’s own age stays the per-provider freshness signal. |
| Add provider | One search above the tabs reaches all four at once — Accounts, Free, Local, Paid. While a query is live the results are grouped by tab with a count each, and the selected tab stays put rather than jumping. Press ArrowDown in the search box to focus the first available result action, skipping disabled buttons; if no action is available, focus stays in the search box. Local runtimes (Ollama, vLLM, LM Studio, LiteLLM) have their own tab, and a long provider note clamps to two lines with the full text one click away. Claude Code CLI (subscription) sits under Paid with a Subscription CLI badge, and selecting it warns that it bills the signed-in Claude subscription through claude -p and never uses an API key (use anthropic-apikey for key billing). |
| 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. The page distinguishes a catalog saved on the hub, a catalog fetched by this client, and activation in a running client. A fetch timestamp does not prove it includes the latest hub save, and runtime activation is shown as unverified. 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. The Usage page’s Models table also breaks each model down into input tokens, output tokens, cache hits, cache writes, and cache hit rate; a dash means cache telemetry for that metric is unavailable. 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. |
| Stop | Gracefully stop the proxy and installed background service, restore native Codex, and exit (POST /api/stop). On Windows with the Task Scheduler backend the dashboard refuses and asks you to run ocx stop instead: that wrapper can respawn the proxy after the task ends, and only a stop running outside this process can verify the restart window before restoring your client config. Nothing is changed when it refuses. It also refuses for a proxy the OpenCodex desktop app runs, which the app would start again: use the app’s tray Stop proxy or Quit there. |
If some usage records cannot be included, the Usage page, Dashboard, provider workspace, provider catalog, and API key views show a warning even when no readable records remain. Counts, dates, and usage rankings reflect readable records only. Models → Most used snapshot → Apply order refuses to save an incomplete snapshot; choose another order or repair the history before retrying.
Account selection
Section titled “Account selection”Account selection is shared with request routing. Selecting an OAuth account takes effect on the next request even when a pool is enabled. A healthy selection is not replaced merely because another generic OAuth account has more unused quota. If the account returns 429, automatic failover can still select another usable account with the pool off. A committed automatic selection updates the dashboard immediately; account changes do not wait for the quota refresh timer. Requests already sent upstream retain their original credentials.
Filtering request logs
Section titled “Filtering request logs”A proxy replay refusal keeps HTTP 429 and error code upstream_reset_replay_refused, but
its request and attempt failure cause is transport-ambiguous, with resend permission
refused-ambiguous: the upstream may already have processed the turn. A genuine provider
429 remains rate-limit with resend permission permitted.
A Codex move to another account after HTTP 402 appears as Account quota exhausted (402) in the attempt’s recovery details and counts as a quota recovery in exported metrics.
Logs filters combine surface, intercepted requests, provider, exact model, status, time,
speed, and conversation ID over the currently loaded request ring. Provider and model
choices also include fallback attempts; model matching ignores case and surrounding spaces
but does not match partial names. Choices that disappear from the ring reset to All.
The conversation field also accepts a pasted codex://threads/<id> deep link, the form a
Codex “copy session link” puts on the clipboard — the wrapper and any link metadata such
as ?hostId=… are ignored and the bare thread id is matched, the same value an
ocx logs filter --conversation search unwraps.
Time windows cover the last 15 minutes, hour, or day and refresh every 30 seconds while the Logs tab is active, even with auto-refresh off. Windows use the proxy timestamp from the logs response and advance with elapsed browser time, so a different browser clock does not shift the cutoff. Older proxies without that timestamp retain the browser-clock fallback until a valid sample is available. Speed uses output tokens per second over the full request duration: below 15, 15 to below 50, or at least 50. Unavailable speed values are excluded when a speed filter is active. Success means 2xx; errors mean 4xx or 5xx.
Logs labels each decode estimate Generation window or After visible output in the list and attempt table, and uses Output rate during generation (est.) or Output rate after first visible output (est.) in request details. Older cached responses without a timing method show Timing unknown and Output rate (est.; timing method unknown) in details. A visible caption below the detail rate explains the timing method; an unavailable rate has no caption. When the proxy observed both ends, it is output tokens over the generation window: from the first output item or block, reasoning included, to the last output delta. Older rows without that window use the time after the first visible token until the end of the request instead. Both are proxy-side observations, not the provider’s internal token timing, so the value is always an estimate, and a window under one second shows as unavailable. Both numerators include reported reasoning output tokens, so the two timing bases are not directly comparable. Historical logs are not rewritten and missing generation timestamps are not invented. The end-to-end tok/s column and speed filter above are not affected.
Active filters show the matching count out of the loaded total. Reset filters restores all rows and returns keyboard focus to the All surface control; “No matching requests” differs from an empty log ring. Use arrow keys or Home/End in the surface selector. These controls do not query historical records beyond the loaded ring.
The language picker includes Português (Brazilian Portuguese, pt-BR). Portuguese browser languages select it automatically unless a supported language preference is already saved.
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.
Cost values in Logs and Usage are API list-price equivalents calculated from reported tokens. For a custom usage interval, the server must confirm the exact requested start and end times. If an older running proxy does not support those bounds, the dashboard and CLI reject its report; upgrade and restart that proxy before retrying. Resetting a manual model price affects only that model, preserving other rates saved independently. The Usage summary and Models/Providers tables show end-to-end output throughput from usage history for the selected range and filters: summed output tokens divided by summed wall-clock seconds, not an average of individual rates. The sample count is measured attempts (or requests for legacy rows without attempts); samples lacking positive finite output tokens or duration are excluded. An em dash means no sample qualified or the server returned an unusable rate. This includes pre-decode waiting and is not estimated decode speed.
The Usage Models and Providers tables show the estimated priced portion for each row. Requests
without a matching price or usable usage are counted as excluded beside that amount when the proxy
reports pricing coverage fields. Usage that was only partly measured (unreported or unsupported)
is not priced, and one excluded part excludes the whole request from that row’s priced count. A
leg that reached no upstream, or that upstream refused with an HTTP error before any output, spent
nothing and does not; a client cancellation (499), or a leg whose response began or ended without
a status but reported no usage, may have been billed, so it does. A refusal followed by a measured
successful send contributes that send’s cost to the summary even when the aggregate usage status
is unreported. A row with only excluded requests shows an em dash with that count
instead of a zero-dollar estimate, and a displayed zero-dollar value is a confirmed priced estimate.
An older proxy that reports no coverage fields keeps a bare em dash with no count, so missing
coverage stays distinct from an all-excluded row and from a confirmed zero estimate.
These are not billing receipts or evidence of an actual charge; subscription usage or provider credits
may apply instead.
Provider model rows may include unresolved requested model usage: the saved route sent the
requested name unchanged to the default provider. These tokens belong to that serving provider,
not necessarily the vendor named in the request. The dashboard preserves the original name and
usage rather than guessing which model ran. For slash-containing unresolved names, a different
vendor’s model price alone is not enough to estimate cost; an exact provider or configured price
still applies. Model shares are calculated within the selected provider. Requests for an unknown
reserved policy/ name now fail before reaching an upstream provider; historical usage is retained.
The selected provider’s Overview and Usage tabs show Current account usage below the usage statistics. Accounts and API keys show each supported credential’s own quota, including credit balances. The provider-wide overview still shows pooled capacity where available; it is not substituted for a missing current-account reading. Unsupported lookup, no passive observation yet, loading, failed lookup with last-known values, and measured zero are separate states. Quota check completed means the read settled—not that a passive observation became new or that every upstream measurement was refreshed.
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.
Switches respond immediately so you can keep changing models while saves run in the background in click order. Saved feedback appears after the queue finishes and the list is reconciled with the server. Failed saves restore the server’s state when it can be read and show an error. Wait for that feedback before leaving Models or changing servers: unsent queued changes are discarded on departure.
Managing models in a provider workspace
Section titled “Managing models in a provider workspace”In a provider’s Models tab, Delete removes the stored custom definition. An underlying native or live-discovered model may then appear again, so the model count can stay the same. Hide changes catalog visibility only: it does not delete the definition or change direct routing policy. Use Manage visibility in Models to open the Models page and restore visibility, even when the provider tab has no rows left.
Add saves a custom definition; it does not clear an existing hide or provider selection rule. A saved model can therefore remain hidden. If the model is already known, manage its visibility in Models. A confirmed save with a failed catalog refresh is still saved: follow the refresh message instead of adding it again. If the change cannot be confirmed, refresh the model state before retrying.
The provider’s model count is the number of unique, non-disabled entries in the current model inventory returned by the server, before search or display truncation. It is not the provider allowlist size, a live-discovery count, or proof that an entry was discovered upstream. Selection badges and discovery information remain separate from that count.
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.
Remote Hub sessions, keys, and usage
Section titled “Remote Hub sessions, keys, and usage”In Connect → API Keys, every row of the key table has its own delete button, which asks for confirmation in place. Clicking a key can show its full value with a Copy button only from an independently authorized session.
Reading an existing API key
Section titled “Reading an existing API key”Showing a stored key’s full value requires a dashboard session established by explicit pairing or
trusted Tailscale identity. An automatic local dashboard session can still show the masked key list,
but cannot reveal existing values; a raw admin token cannot call this session-only action either.
Use the existing dashboard pairing flow to establish an
operator-authorized session before requesting a stored value; a refused reveal states the requirement
on the page. The local pairing form is offered only by a same-origin standalone dashboard opened
over HTTP at the literal 127.0.0.1 or [::1] address. The origin must match the server’s
configured literal loopback bind. On http://localhost:<port> (or another localhost alias),
check the server’s bind: reopen http://127.0.0.1:<port> for a 127.0.0.1 bind or
http://[::1]:<port> for a ::1 bind, then pair there. A server configured with hostname
localhost has no eligible local pairing origin. The dashboard cannot infer the configured
bind from an alias URL, so it shows the generic denial without suggesting a replacement URL.
Use the existing remote-hub pairing flow or trusted Tailscale sign-in where available.
Pair again if the authorized session expires or is revoked. Ordinary dashboard sign-in,
key creation, rotation, and deletion are unchanged.
After pairing, click the key again to request its value. Pairing itself never reveals a key. Displayed key values and copy feedback are cleared when the shared session is cleared or replaced, pairing starts, you leave this panel, hide the browser tab or desktop window, or switch servers. This also clears newly created and replacement values, so copy a one-time value before leaving the panel. If creation or rotation succeeds after the session or view changes, its one-time value stays hidden. After switching servers, returning to the original server revalidates its key list. Pending rotations remain available for explicit commit or abort.
The dashboard’s management plane is separate from direct client→hub model traffic. Connect → API Keys shows pending rotations, displays a replacement secret only once, and requires explicit commit or abort. Browser logout invalidates only the current remote session. Connected usage is the hub store filtered by the client’s apiKeyId; disconnected usage is local, with no mirroring.
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:
Pool mode selects across the main and added Codex accounts; Direct uses only the caller/main login.
In-flight requests keep their captured credentials, and a 401/403 reauthentication or 429 cooldown
may clear affinity and rotate to another eligible Pool account. This is separate from openai-apikey
and other providers.
- Manually choosing an account applies immediately: an already-bound thread moves to it on its next request, and only requests already in flight keep the account they captured. A manual choice is also pinned: the card shows a PINNED badge, and a higher selection order cannot preempt that account until it is drained, you select another account, or you change any account’s selection order. Selecting the badge again asks to release the pin: confirming clears the selection, bound threads keep their captured account, and new or unbound requests return to the selection order and thresholds. If the pin changes while confirmation is open, that confirmation cannot release the newer pin. After a refused or uncertain release, the account view is refreshed; review the current selection before confirming again.
- Each account card carries a Selection order control (First, Earlier, Normal, Later, Last).
Higher order is used first, and the pool drops to a lower order only once every account above it is
drained or unavailable. A changed order applies from the next unbound request and never moves a
thread that is already bound. The Codex Desktop (main) account is ordered like any other, so it can
be set to Last and kept as the reserve. An order set from
ocx account priorityoutside those five presets stays visible and selectable on the card. - Thread affinity prevents per-request flapping. With
pool.cacheAffinityon (the default), a long-running thread is not rebound merely because usage crossed the threshold; it stays until the account is exhausted or cannot serve, and then only onto an account with genuine quota headroom and strictly lower usage. Set the flagfalseto restore threshold rebinding, still only onto such a destination. - 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.
- Pool request logs use opaque labels such as
p3fa91c, never account emails. - Each account card also shows that stable log label, the observed 30-day token total, an approximate
API-equivalent cost using currently configured display pricing, and the fraction of attempts with
measured usage. Active user
modelCostsoverlays take priority over bundled verified catalog and price fallbacks, and historical usage is re-estimated from the pricing active when the summary is read. The cost is an estimate for reconciliation, not a ChatGPT Plus/Pro subscription invoice. Historical bareopenairows that predate explicit attribution remain ambiguous rather than being assigned to the current main account. - Target a specific Codex account from the model picker is an explicit opt-in. When enabled,
ordinary supported GPT picker rows are replaced by one entry per public account selector.
Choosing one locks that conversation to the mapped account: it does not rotate, fall back, or
change the active Pool account. The built-in Codex App login has its own selector; generated maps
normally use
main, with a collision-safe suffix such asmain-2when needed. Added accounts receive stable, privacy-safe labels, and existing custom selector labels are preserved. Existing conversations and saved model selections continue routing. Turning the setting off hides generated picker entries without deleting accounts, selectors, or exact routes. Plain GPT model ids continue to use the configured Pool or Direct behavior. - Account add, remove, and picker-setting changes are saved before the model catalog is refreshed.
If that bounded refresh cannot finish, the dashboard shows an amber success-with-recovery notice;
run
ocx syncto retry. The account or setting change itself remains saved.
The Providers overview separately summarizes Pool-mode usage as a display-only weighted capacity estimate, alongside the effective account’s raw quota and the next capacity recovery. See Providers overview pool capacity for the visible fields, incomplete-coverage meaning, and routing boundary.
Starring is yours to decide, not an agent’s
Section titled “Starring is yours to decide, not an agent’s”The sidebar’s star button — and the one-time question ocx start asks in an interactive
terminal — goes through your own gh login. opencodex holds no GitHub token, and the
only thing it learns is your yes or no.
Because that writes to your GitHub account, agent-driven callers are refused rather than allowed to answer for you:
ocx startandocx service installskip the prompt entirely when an agent or CI harness is driving them (CLAUDECODE,CODEX_THREAD_ID,CURSOR_TRACE_ID,CI, and similar). The one-time marker stays unwritten, so the real prompt still shows up on your next hand-typed run. The agent is told to ask you instead — and to ask as a plain Yes/No choice you have to answer, not as a soft aside it can slip past you. If you never get around to answering, the agent is told to re-ask rather than treat your silence as a no.POST /api/github/staranswers403withcode: "agent_consent_required"when the proxy runs under an agent session and the request has no dashboard browser session. Possessing the admin token is not consent: an agent on your machine can read that file.- The dashboard button keeps working normally. A real click carries same-origin session evidence, so it is recognized as you even when an agent started the proxy.
- Saying no ends it. Nothing is persisted and nothing is added to any model prompt to nudge you later.
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 update Codex autostart, stream/memory settings, and account-targeting picker visibility. |
GET / POST /api/github/star |
Read the gh-derived star state, or star the repository. The POST is refused with 403 agent_consent_required for agent-driven callers without a dashboard session. |
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, set the default, or remove providers. PATCH uses standalone { "setDefault": true } on an enabled provider; POST may include setDefault when creating/replacing (also enabled-only). Deleting the current default reassigns to the first remaining enabled provider when one exists; otherwise the API returns 409 with code: "last_provider" and keeps the current default. |
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. |
GET /api/oauth/accounts?provider=..."a=1 · GET /api/providers/keys?name=..."a=1 |
Read each account or key’s quota where supported, without changing the active credential. Add refresh=1 to bypass settled quota cache; an in-flight same-credential read can be shared. Omit quota=1 for a cheap local list with each row’s quotaMode: probe, passive, or unsupported. Passive reads return existing observations without a network probe. No reading is not the same as 0% used, and quotas for multiple keys are not summed. |
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. |
GET /api/codex-auth/active · PUT /api/codex-auth/accounts/priority |
Read the effective account (including pinned and which account is pinnedAccountId) and set one account’s selection order. |
POST /api/codex-auth/login · GET /api/codex-auth/login-status |
Add a pool account through browser login. |
GET /api/logs?tail=50&limit=20&offset=0&provider=...&status=5xx |
Read recent request metadata with optional tail, provider, and exact/class status filters. With limit/offset, paging walks backward from the newest row (offset=0 returns the latest page). Response shape: { timeZone, generatedAt, total, logs } where total is the filtered row count before pagination. |
GET / PUT /api/subagent-models |
Read or set the five featured spawn_agent override models. |
POST /api/stop |
Stop the proxy/service, restore native Codex, and exit. Refused with respawnable_service on the Windows Task Scheduler backend, with self_unload_service when this proxy is itself the installed launchd/systemd job, and with service_state_unknown when the Task Scheduler state cannot be read; nothing is changed in any of those cases. A dashboard session is also refused, with desktop_supervised, while the OpenCodex desktop app runs the proxy. |
Pairing this browser with a hub
Section titled “Pairing this browser with a hub”Machine enrollment and browser authentication are separate. The pairing panel names the hub and displays an ocx gui pair --origin command for the exact origin currently open in your browser. Run that command on the hub, or send it to the hub operator and request a one-time pairing code. Paste that code into the panel; a data API key or admin token is not a pairing code.
While browser authentication is pending, the dashboard does not recommend restarting a healthy connected client. Completing pairing refreshes the dashboard data immediately, including a previously cached authentication failure. Session expiry returns to pairing; permission denial keeps its own access-settings guidance. Other failed refreshes may show the last received data with a stale-data notice and retry action.
Usage chart keyboard and touch controls
Section titled “Usage chart keyboard and touch controls”Usage heatmap days have one Tab entry point. Use Up/Down for adjacent days and Left/Right for adjacent weeks. Weekly bars expose the same day details on keyboard focus, pointer hover, or touch. Day labels include the date, request count, and token count; tooltip overlays stay within the viewport.
Claude
Section titled “Claude”Claude is a tab inside Connect, whose tab strip places Codex, Claude, Claude Desktop, and Grok Build in that order. Claude has no sub-tabs: it shows the Claude Code settings directly. The page starts with Claude Code CLI first-party, followed by Get started, General (compatibility, agent instructions, and context controls), Background helper model, Model interception, Available models, and finally Claude connection. A sticky Save bar shows No changes or Unsaved changes, with Revert to discard edits and Save to commit them. Both switches apply immediately; Save never changes them. When interception is stopped, the page shows why and offers Start interception, which starts it in place without restarting OpenCodex. The former Code, Settings, and Account sub-tabs are gone; everything the read-only Settings view showed except the intercept port number is on this page.
Claude Desktop is its own Connect tab at #claude/desktop. It contains the Desktop mode picker and model families. One status row shows whether Claude Desktop runs the profile and whether it is saved, with Save and Save & apply beside it.
Claude/Anthropic accounts are managed only on Providers > Anthropic > Accounts: login and the browser option, the account roster with switch, pause, remove, and reauthentication, the paste-code field, account pool settings, and quota. Claude no longer has an Account sub-tab. Provider-level controls such as the default provider, removal, and the enabled switch stay on Providers.
The bookmarks #claude and #claude/code open this page, and the former #claude/settings redirects to #claude/code; #claude/desktop selects the separate Claude Desktop tab. The old #claude/account bookmark redirects to #providers?provider=anthropic&tab=accounts. Legacy #integrations/claude and #integrations/claude/desktop bookmarks still redirect to #claude/code and #claude/desktop, respectively. Arrow keys move between tabs; Home and End select the first and last tab.
Test one provider model
Section titled “Test one provider model”In Providers, Test connection checks discovery connectivity. Use the separate Test completion control to check whether a selected model answers. Choose a model and accept the confirmation that the request may incur provider charges. Cancelling the confirmation sends nothing. Changing the provider or model, or leaving the page, cancels the test and discards a stale result.
The completion test sends Reply exactly OK. once with a 32-token output cap,
a 20-second deadline and a 64 KiB stream limit. It reports TTFT at the first
non-empty text delta, total latency, the upstream-reported model and a match,
mismatch or unknown comparison against the wire model id. Missing or conflicting
metadata is unknown; upstream metadata does not verify model identity. The panel
shows the applicability reason when authentication, adapter or transport settings
cannot be tested. No generated text is displayed, and the diagnostic does not
update routing health, request history or the provider catalog.
The authenticated management endpoint is
POST /api/providers/test-completion?name=<provider> with exactly
{"model":"<model>","confirm":true}. Dashboard sessions require their usual CSRF
token. Invalid input returns 400, unknown providers return 404 and a concurrent
test for the same provider returns 409. Completed diagnostics and applicability
results return 200, including a structured error when inference fails. Destination
policy and provider egress settings apply; redirects, retries and failover are disabled.
For the CLI workflow, see Providers and accounts.

